diff --git a/.gitignore b/.gitignore index 5afd1956..02cec5d8 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ x64/ build/ [Bb]in/ [Oo]bj/ +/.vs # MSTest test Results [Tt]est[Rr]esult*/ diff --git a/Client/FileTransferSettings.cs b/Client/FileTransferSettings.cs index 07b44569..3cfeaa05 100644 --- a/Client/FileTransferSettings.cs +++ b/Client/FileTransferSettings.cs @@ -1,8 +1,9 @@ -using Net.Xmpp.Extensions; -using System; +using System; using System.Collections.Generic; using System.Net; +using Net.Xmpp.Extensions; + namespace Net.Xmpp.Client { /// @@ -13,39 +14,27 @@ public class FileTransferSettings /// /// A reference to the Socks5Bytestreams extension. /// - private Socks5Bytestreams socks5; + private readonly Socks5Bytestreams socks5; /// /// A reference to the SIFileTransfer extension. /// - private SIFileTransfer siFileTransfer; + private readonly SIFileTransfer siFileTransfer; /// /// Determines whether usage of a SOCKS5 proxy server is allowed. /// public bool ProxyAllowed { - get - { - return socks5.ProxyAllowed; - } - - set - { - socks5.ProxyAllowed = value; - } + get => socks5.ProxyAllowed; + + set => socks5.ProxyAllowed = value; } /// /// A collection of user-defined SOCKS5 proxy servers. /// - public ICollection Proxies - { - get - { - return socks5.Proxies; - } - } + public ICollection Proxies => socks5.Proxies; /// /// Defines, along with the Socks5ServerPortTo property, a range of ports @@ -56,15 +45,9 @@ public ICollection Proxies /// Socks5ServerPortTo property. public int Socks5ServerPortFrom { - get - { - return socks5.ServerPortFrom; - } - - set - { - socks5.ServerPortFrom = value; - } + get => socks5.ServerPortFrom; + + set => socks5.ServerPortFrom = value; } /// @@ -77,15 +60,9 @@ public int Socks5ServerPortFrom /// 65535. public int Socks5ServerPortTo { - get - { - return socks5.ServerPortTo; - } - - set - { - socks5.ServerPortTo = value; - } + get => socks5.ServerPortTo; + + set => socks5.ServerPortTo = value; } /// @@ -93,15 +70,9 @@ public int Socks5ServerPortTo /// public bool UseUPnP { - get - { - return socks5.UseUPnP; - } - - set - { - socks5.UseUPnP = value; - } + get => socks5.UseUPnP; + + set => socks5.UseUPnP = value; } /// @@ -113,15 +84,9 @@ public bool UseUPnP /// public DnsEndPoint StunServer { - get - { - return socks5.StunServer; - } - - set - { - socks5.StunServer = value; - } + get => socks5.StunServer; + + set => socks5.StunServer = value; } /// @@ -130,15 +95,9 @@ public DnsEndPoint StunServer /// public bool ForceInBandBytestreams { - get - { - return siFileTransfer.ForceInBandBytestreams; - } - - set - { - siFileTransfer.ForceInBandBytestreams = value; - } + get => siFileTransfer.ForceInBandBytestreams; + + set => siFileTransfer.ForceInBandBytestreams = value; } /// @@ -153,8 +112,8 @@ public bool ForceInBandBytestreams internal FileTransferSettings(Socks5Bytestreams socks5, SIFileTransfer siFileTransfer) { - socks5.ThrowIfNull("socks5"); - siFileTransfer.ThrowIfNull("siFileTransfer"); + socks5.ThrowIfNull(nameof(socks5)); + siFileTransfer.ThrowIfNull(nameof(siFileTransfer)); this.socks5 = socks5; this.siFileTransfer = siFileTransfer; } diff --git a/Client/XmppClient.cs b/Client/XmppClient.cs index f741cfcb..40f0804c 100644 --- a/Client/XmppClient.cs +++ b/Client/XmppClient.cs @@ -1,14 +1,15 @@ -using Net.Xmpp.Extensions; -using Net.Xmpp.Extensions.Dataforms; -using Net.Xmpp.Im; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Net.Security; -using System.Xml; using System.Threading.Tasks; +using Net.Xmpp.Extensions; +using Net.Xmpp.Extensions.Dataforms; +using Net.Xmpp.Im; + namespace Net.Xmpp.Client { /// @@ -27,183 +28,170 @@ public class XmppClient : IDisposable /// private bool disposed; - /// - /// The instance of the XmppIm class used for implementing the basic messaging - /// and presence functionality. - /// - private XmppIm im; - /// /// Provides access to the 'Message Archiving' XMPP extension functionality. /// - private MessageArchiving messageArchiving; + private readonly MessageArchiving messageArchiving; /// /// Provices access to the 'Message Archive management' XMPP extension functionality. /// - private MessageArchiveManagement messageArchiveManagement; - + private readonly MessageArchiveManagement messageArchiveManagement; + /// /// Provides access to the 'Software Version' XMPP extension functionality. /// - private SoftwareVersion version; + private readonly SoftwareVersion version; /// /// Provides access to the 'Service Discovery' XMPP extension functionality. /// - private ServiceDiscovery sdisco; + private readonly ServiceDiscovery sdisco; /// /// Provides access to the 'Entity Capabilities' XMPP extension functionality. /// - private EntityCapabilities ecapa; + private readonly EntityCapabilities ecapa; /// /// Provides access to the 'Ping' XMPP extension functionality. /// - private Ping ping; + private readonly Ping ping; /// /// Provides access to the 'Custom Iq Extension' functionality /// - private CustomIqExtension cusiqextension; + private readonly CustomIqExtension cusiqextension; /// /// Provides access to the 'Attention' XMPP extension functionality. /// - private Attention attention; + private readonly Attention attention; /// /// Provides access to the 'Entity Time' XMPP extension functionality. /// - private EntityTime time; + private readonly EntityTime time; /// /// Provides access to the 'Blocking Command' XMPP extension functionality. /// - private BlockingCommand block; + private readonly BlockingCommand block; /// /// Provides access to the 'Personal Eventing Protocol' extension. /// - private Pep pep; + private readonly Pep pep; /// /// Provides access to the 'User Tune' XMPP extension functionality. /// - private UserTune userTune; + private readonly UserTune userTune; /// /// Provides access to the "Multi-User Chat" XMPP extension functionality. /// - private MultiUserChat groupChat; + private readonly MultiUserChat groupChat; #if WINDOWSPLATFORM - /// - /// Provides access to the 'User Avatar' XMPP extension functionality. - /// - UserAvatar userAvatar; + /// + /// Provides access to the 'User Avatar' XMPP extension functionality. + /// + private UserAvatar userAvatar; #endif /// /// Provides access to the 'User Mood' XMPP extension functionality. /// - private UserMood userMood; + private readonly UserMood userMood; /// /// Provides access to the 'Data Forms' XMPP extension functionality. /// - private DataForms dataForms; + private readonly DataForms dataForms; /// /// Provides access to the 'Feature Negotiation' XMPP extension. /// - private FeatureNegotiation featureNegotiation; + private readonly FeatureNegotiation featureNegotiation; /// /// Provides access to the 'Stream Initiation' XMPP extension. /// - private StreamInitiation streamInitiation; + private readonly StreamInitiation streamInitiation; /// /// Provides access to the 'SI File Transfer' XMPP extension. /// - private SIFileTransfer siFileTransfer; + private readonly SIFileTransfer siFileTransfer; /// /// Provides access to the 'In-Band Bytestreams' XMPP extension. /// - private InBandBytestreams inBandBytestreams; + private readonly InBandBytestreams inBandBytestreams; /// /// Provides access to the 'User Activity' XMPP extension. /// - private UserActivity userActivity; + private readonly UserActivity userActivity; /// /// Provides access to the 'Socks5 Bytestreams' XMPP extension. /// - private Socks5Bytestreams socks5Bytestreams; + private readonly Socks5Bytestreams socks5Bytestreams; /// /// Provides access to the 'Server IP Check' XMPP extension. /// - private ServerIpCheck serverIpCheck; + private readonly ServerIpCheck serverIpCheck; /// /// Provides access to the 'In-Band Registration' XMPP extension. /// - private InBandRegistration inBandRegistration; + private readonly InBandRegistration inBandRegistration; /// /// Provides access to the 'Chat State Nofitications' XMPP extension. /// - private ChatStateNotifications chatStateNotifications; + private readonly ChatStateNotifications chatStateNotifications; /// /// Provides access to the 'Bits of Binary' XMPP extension. /// - private BitsOfBinary bitsOfBinary; + private readonly BitsOfBinary bitsOfBinary; /// /// Provides vcard Based Avatar functionality /// - private VCardAvatars vcardAvatars; + private readonly VCardAvatars vcardAvatars; /// /// Provides vcard functionality /// - private VCards vcard; - + private readonly VCards vcard; + /// /// Provides the Message Carbons extension /// - private MessageCarbons messageCarbons; + private readonly MessageCarbons messageCarbons; /// /// Provides the Jabber Search extension /// - private JabberSearch search; + private readonly JabberSearch search; /// /// Provides the HTTP File Upload extension /// - private HTTPFileUpload httpUpload; + private readonly HTTPFileUpload httpUpload; /// /// The hostname of the XMPP server to connect to. /// public string Hostname { - get - { - return im.Hostname; - } - - set - { - im.Hostname = value; - } + get => Im.Hostname; + set => Im.Hostname = value; } /// @@ -211,15 +199,8 @@ public string Hostname /// public int Port { - get - { - return im.Port; - } - - set - { - im.Port = value; - } + get => Im.Port; + set => Im.Port = value; } /// @@ -228,15 +209,8 @@ public int Port /// public string Username { - get - { - return im.Username; - } - - set - { - im.Username = value; - } + get => Im.Username; + set => Im.Username = value; } /// @@ -244,15 +218,8 @@ public string Username /// public string Password { - get - { - return im.Password; - } - - set - { - im.Password = value; - } + get => Im.Password; + set => Im.Password = value; } /// @@ -260,107 +227,56 @@ public string Password /// public bool Tls { - get - { - return im.Tls; - } - - set - { - im.Tls = value; - } + get => Im.Tls; + set => Im.Tls = value; } /// /// A delegate used for verifying the remote Secure Sockets Layer (SSL) /// certificate which is used for authentication. /// - public RemoteCertificateValidationCallback Validate + public RemoteCertificateValidationCallback? Validate { - get - { - return im.Validate; - } - - set - { - im.Validate = value; - } + get => Im.Validate; + set => Im.Validate = value; } /// /// Determines whether the session with the server is TLS/SSL encrypted. /// - public bool IsEncrypted - { - get - { - return im.IsEncrypted; - } - } + public bool IsEncrypted => Im.IsEncrypted; /// /// The address of the Xmpp entity. /// - public Jid Jid - { - get - { - return im.Jid; - } - } + public Jid? Jid => Im.Jid; /// /// Determines whether the instance is connected to the XMPP server. /// - public bool Connected - { - get - { - if (im != null) - { - return im.Connected; - } - else - { - return false; - } - } - } + public bool Connected => Im?.Connected == true; /// /// The event that is raised when a connection state changed. /// - public event EventHandler OnConnect + public event EventHandler? OnConnect { - add - { - im.OnConnect += value; - } - remove - { - im.OnConnect -= value; - } + add => Im.OnConnect += value; + remove => Im.OnConnect -= value; } /// /// Determines whether the instance has been authenticated. /// - public bool Authenticated - { - get - { - return im.Authenticated; - } - } + public bool Authenticated => Im.Authenticated; /// /// The default IQ Set Time out in Milliseconds. -1 means no timeout /// public int DefaultTimeOut { - get { return im.DefaultTimeOut; } - set { im.DefaultTimeOut = value; } + get => Im.DefaultTimeOut; + set => Im.DefaultTimeOut = value; } /// @@ -368,182 +284,112 @@ public int DefaultTimeOut /// public bool DebugStanzas { - get { return im.DebugStanzas; } - set { im.DebugStanzas = value; } + get => Im.DebugStanzas; + set => Im.DebugStanzas = value; } /// /// Contains settings for configuring file-transfer options. /// - public FileTransferSettings FileTransferSettings - { - get; - private set; - } + public FileTransferSettings? FileTransferSettings { get; private set; } /// /// The underlying XmppIm instance. /// - public XmppIm Im - { - get - { - return im; - } - } + public XmppIm Im { get; private set; } /// /// A callback method to invoke when a request for a subscription is received /// from another XMPP user. /// /// - public SubscriptionRequest SubscriptionRequest + public SubscriptionRequest? SubscriptionRequest { - get - { - return im.SubscriptionRequest; - } - - set - { - im.SubscriptionRequest = value; - } + get => Im.SubscriptionRequest; + set => Im.SubscriptionRequest = value; } /// /// A callback method to invoke when a request for voice is received /// from another XMPP user. /// - public RegistrationCallback VoiceRequestedInGroupChat + public RegistrationCallback? VoiceRequestedInGroupChat { - get - { - return groupChat.VoiceRequested; - } - - set - { - groupChat.VoiceRequested = value; - } + get => groupChat?.VoiceRequested; + set { if (groupChat is not null) groupChat.VoiceRequested = value; } } /// /// The event that is raised when a status notification has been received. /// - public event EventHandler StatusChanged + public event EventHandler? StatusChanged { - add - { - im.Status += value; - } - remove - { - im.Status -= value; - } + add => Im.Status += value; + remove => Im.Status -= value; } /// /// The event that is raised when a mood notification has been received. /// - public event EventHandler MoodChanged + public event EventHandler? MoodChanged { - add - { - userMood.MoodChanged += value; - } - remove - { - userMood.MoodChanged -= value; - } + add => userMood.MoodChanged += value; + remove => userMood.MoodChanged -= value; } /// /// The event that is raised when an activity notification has been received. /// - public event EventHandler ActivityChanged + public event EventHandler? ActivityChanged { - add - { - userActivity.ActivityChanged += value; - } - remove - { - userActivity.ActivityChanged -= value; - } + add => userActivity.ActivityChanged += value; + remove => userActivity.ActivityChanged -= value; } #if WINDOWSPLATFORM - /// - /// The event that is raised when a contact has updated his or her avatar. - /// - public event EventHandler AvatarChanged { - add { - userAvatar.AvatarChanged += value; - } - remove { - userAvatar.AvatarChanged -= value; - } - } + /// + /// The event that is raised when a contact has updated his or her avatar. + /// + public event EventHandler? AvatarChanged { + add => userAvatar.AvatarChanged += value; + remove => userAvatar.AvatarChanged -= value; + } #endif /// /// The event that is raised when a contact has published tune information. /// - public event EventHandler Tune + public event EventHandler? Tune { - add - { - userTune.Tune += value; - } - remove - { - userTune.Tune -= value; - } + add => userTune.Tune += value; + remove => userTune.Tune -= value; } /// /// The event that is raised when a chat message is received. /// - public event EventHandler Message + public event EventHandler? Message { - add - { - im.Message += value; - } - remove - { - im.Message -= value; - } + add => Im.Message += value; + remove => Im.Message -= value; } - + /// /// The event that is raised when an error message is received. /// - public event EventHandler ErrorMessage + public event EventHandler? ErrorMessage { - add - { - im.ErrorMessage += value; - } - remove - { - im.ErrorMessage -= value; - } + add => Im.ErrorMessage += value; + remove => Im.ErrorMessage -= value; } /// /// The event that is raised when the subject is changed in a group chat. /// - public event EventHandler GroupChatSubjectChanged + public event EventHandler? GroupChatSubjectChanged { - add - { - groupChat.SubjectChanged += value; - } - remove - { - groupChat.SubjectChanged -= value; - } + add => groupChat.SubjectChanged += value; + remove => groupChat.SubjectChanged -= value; } /// @@ -558,173 +404,107 @@ public void EditRoomSubject(Jid room, string subject) /// /// The event that is raised when a participant's presence is changed in a group chat. /// - public event EventHandler GroupPresenceChanged + public event EventHandler? GroupPresenceChanged { - add - { - groupChat.PrescenceChanged += value; - } - remove - { - groupChat.PrescenceChanged -= value; - } + add => groupChat.PrescenceChanged += value; + remove => groupChat.PrescenceChanged -= value; } /// /// The event that is raised when an invite to a group chat is received. /// - public event EventHandler GroupInviteReceived + public event EventHandler? GroupInviteReceived { - add - { - groupChat.InviteReceived += value; - } - remove - { - groupChat.InviteReceived -= value; - } + add => groupChat.InviteReceived += value; + remove => groupChat.InviteReceived -= value; } /// /// The event that is raised when an invite to a group chat is declined. /// - public event EventHandler GroupInviteDeclined + public event EventHandler? GroupInviteDeclined { - add - { - groupChat.InviteWasDeclined += value; - } - remove - { - groupChat.InviteWasDeclined -= value; - } + add => groupChat.InviteWasDeclined += value; + remove => groupChat.InviteWasDeclined -= value; } /// /// The event that is raised when the server responds with an error in relation to a group chat. /// - public event EventHandler GroupMucError + public event EventHandler? GroupMucError { - add - { - groupChat.MucErrorResponse += value; - } - remove - { - groupChat.MucErrorResponse -= value; - } + add => groupChat.MucErrorResponse += value; + remove => groupChat.MucErrorResponse -= value; } /// /// The event that is raised periodically for every file-transfer operation to /// inform subscribers of the progress of the operation. /// - public event EventHandler FileTransferProgress + public event EventHandler? FileTransferProgress { - add - { - siFileTransfer.FileTransferProgress += value; - } - remove - { - siFileTransfer.FileTransferProgress -= value; - } + add => siFileTransfer.FileTransferProgress += value; + remove => siFileTransfer.FileTransferProgress -= value; } /// /// The event that is raised when an on-going file-transfer has been aborted /// prematurely, either due to cancellation or error. /// - public event EventHandler FileTransferAborted + public event EventHandler? FileTransferAborted { - add - { - siFileTransfer.FileTransferAborted += value; - } - remove - { - siFileTransfer.FileTransferAborted -= value; - } + add => siFileTransfer.FileTransferAborted += value; + remove => siFileTransfer.FileTransferAborted -= value; } /// /// The event that is raised when the chat-state of an XMPP entity has /// changed. /// - public event EventHandler ChatStateChanged + public event EventHandler? ChatStateChanged { - add - { - chatStateNotifications.ChatStateChanged += value; - } - remove - { - chatStateNotifications.ChatStateChanged -= value; - } + add => chatStateNotifications.ChatStateChanged += value; + remove => chatStateNotifications.ChatStateChanged -= value; } /// /// The event that is raised when the roster of the user has been updated, /// i.e. a contact has been added, removed or updated. /// - public event EventHandler RosterUpdated + public event EventHandler? RosterUpdated { - add - { - im.RosterUpdated += value; - } - remove - { - im.RosterUpdated -= value; - } + add => Im.RosterUpdated += value; + remove => Im.RosterUpdated -= value; } /// /// The event that is raised when a user or resource has unsubscribed from /// receiving presence notifications of the JID associated with this instance. /// - public event EventHandler Unsubscribed + public event EventHandler? Unsubscribed { - add - { - im.Unsubscribed += value; - } - remove - { - im.Unsubscribed -= value; - } + add => Im.Unsubscribed += value; + remove => Im.Unsubscribed -= value; } /// /// The event that is raised when a subscription request made by the JID /// associated with this instance has been approved. /// - public event EventHandler SubscriptionApproved + public event EventHandler? SubscriptionApproved { - add - { - im.SubscriptionApproved += value; - } - remove - { - im.SubscriptionApproved -= value; - } + add => Im.SubscriptionApproved += value; + remove => Im.SubscriptionApproved -= value; } /// /// The event that is raised when a subscription request made by the JID /// associated with this instance has been refused. /// - public event EventHandler SubscriptionRefused + public event EventHandler? SubscriptionRefused { - add - { - im.SubscriptionRefused += value; - } - remove - { - im.SubscriptionRefused -= value; - } + add => Im.SubscriptionRefused += value; + remove => Im.SubscriptionRefused -= value; } /// @@ -732,14 +512,8 @@ public event EventHandler SubscriptionRefused /// public event EventHandler Error { - add - { - im.Error += value; - } - remove - { - im.Error -= value; - } + add => Im.Error += value; + remove => Im.Error -= value; } /// @@ -764,61 +538,10 @@ public event EventHandler Error /// is not a valid port number. /// Use this constructor if you wish to connect to an XMPP server using /// an existing set of user credentials. - public XmppClient(string hostname, string username, string password, - int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null, string serveradress = "") + public XmppClient(string hostname, string username, string password, int port = 5222, bool tls = true, + RemoteCertificateValidationCallback? validate = null, string serveradress = "", string? resource = null, int defaultTimeoutMs = -1, int defaultMaxRetries = -1) + : this(new XmppIm(hostname, username, password, port, tls, validate, serveradress, resource, defaultTimeoutMs, defaultMaxRetries)) { - im = new XmppIm(hostname, username, password, port, tls, validate, serveradress); - // Initialize the various extension modules. - LoadExtensions(); - } - - /// - /// Initializes a new instance of the XmppClient class. - /// - /// The hostname of the XMPP server to connect to. - /// The port number of the XMPP service of the server. - /// If true the session will be TLS/SSL-encrypted if the server - /// supports TLS/SSL-encryption. - /// A delegate used for verifying the remote Secure Sockets - /// Layer (SSL) certificate which is used for authentication. Can be null if not - /// needed. - /// Adress if hostname is diferrent from resolution name - /// The hostname parameter is - /// null. - /// The hostname parameter is the empty - /// string. - /// The value of the port parameter - /// is not a valid port number. - /// Use this constructor if you wish to register an XMPP account using - /// the in-band account registration process supported by some servers. - public XmppClient(string hostname, int port = 5222, bool tls = true, - RemoteCertificateValidationCallback validate = null, string serverAdress = "") - { - im = new XmppIm(hostname, port, tls, validate, serverAdress); - LoadExtensions(); - } - - /// - /// Establishes a connection to the XMPP server. - /// - /// The resource identifier to bind with. If this is null, - /// a resource identifier will be assigned by the server. - /// The user's roster (contact list). - /// An - /// authentication error occured while trying to establish a secure connection, or - /// the provided credentials were rejected by the server, or the server requires - /// TLS/SSL and the Tls property has been set to false. - /// There was a failure while writing to or - /// reading from the network. If the InnerException is of type SocketExcption, use - /// the ErrorCode property to obtain the specific socket error code. - /// The XmppClient object has been - /// disposed. - /// An XMPP error occurred while negotiating the - /// XML stream with the server, or resource binding failed, or the initialization - /// of an XMPP extension failed. - public void Connect(string resource = null) - { - im.Connect(resource); } /// @@ -843,7 +566,7 @@ public void Connect(string resource = null) /// of an XMPP extension failed. public void Authenticate(string username, string password) { - im.Autenticate(username, password); + Im.Autenticate(username, password); } /// @@ -864,7 +587,7 @@ public void Authenticate(string username, string password) /// of an XMPP extension failed. public void SimpleAutenticate(string username, string password) { - im.SimpleAutenticate(username, password); + Im.SimpleAutenticate(username, password); } /// @@ -873,7 +596,7 @@ public void SimpleAutenticate(string username, string password) public void SetPresence() { // Send initial presence. - im.SetPresence(); + Im.SetPresence(); } /// @@ -881,7 +604,7 @@ public void SetPresence() /// public void Reconnect() { - im.Reconnect(); + Im.Reconnect(); } /// @@ -915,14 +638,14 @@ public void EnableCarbons() /// The XmppClient object has been /// disposed. /// - public void SendMessage(Jid to, string body, string subject = null, - string thread = null, MessageType type = MessageType.Normal, - CultureInfo language = null) + public void SendMessage(Jid to, string body, string? subject = null, + string? thread = null, MessageType type = MessageType.Normal, + CultureInfo? language = null) { AssertValid(); - to.ThrowIfNull("to"); + to.ThrowIfNull(nameof(to)); body.ThrowIfNullOrEmpty("body"); - im.SendMessage(to, body, subject, thread, type, language); + Im.SendMessage(to, body, subject, thread, type, language); } /// @@ -956,13 +679,13 @@ public void SendMessage(Jid to, string body, string subject = null, /// /// public void SendMessage(Jid to, IDictionary bodies, - IDictionary subjects = null, string thread = null, - MessageType type = MessageType.Normal, CultureInfo language = null) + IDictionary? subjects = null, string? thread = null, + MessageType type = MessageType.Normal, CultureInfo? language = null) { AssertValid(); - to.ThrowIfNull("to"); - bodies.ThrowIfNull("bodies"); - im.SendMessage(to, bodies, subjects, thread, type, language); + to.ThrowIfNull(nameof(to)); + bodies.ThrowIfNull(nameof(bodies)); + Im.SendMessage(to, bodies, subjects, thread, type, language); } /// @@ -980,8 +703,8 @@ public void SendMessage(Jid to, IDictionary bodies, public void SendMessage(Message message) { AssertValid(); - message.ThrowIfNull("message"); - im.SendMessage(message); + message.ThrowIfNull(nameof(message)); + Im.SendMessage(message); } /// @@ -1004,11 +727,11 @@ public void SendMessage(Message message) /// the XMPP server. /// The XmppClient object has been /// disposed. - public void SetStatus(Availability availability, string message = null, - sbyte priority = 0, CultureInfo language = null) + public void SetStatus(Availability availability, string? message = null, + sbyte priority = 0, CultureInfo? language = null) { AssertValid(); - im.SetStatus(availability, message, 0, language); + Im.SetStatus(availability, message, 0, language); } /// @@ -1035,7 +758,7 @@ public void SetStatus(Availability availability, Dictionary messages, sbyte priority = 0) { AssertValid(); - im.SetStatus(availability, messages, priority); + Im.SetStatus(availability, messages, priority); } /// @@ -1055,8 +778,8 @@ public void SetStatus(Availability availability, public void SetStatus(Status status) { AssertValid(); - status.ThrowIfNull("status"); - im.SetStatus(status); + status.ThrowIfNull(nameof(status)); + Im.SetStatus(status); } /// @@ -1081,7 +804,7 @@ public void SetStatus(Status status) public Roster GetRoster() { AssertValid(); - return im.GetRoster(); + return Im.GetRoster(); } /// @@ -1106,14 +829,14 @@ public Roster GetRoster() /// error condition. /// The server returned invalid data or another /// unspecified XMPP error occurred. - public void AddContact(Jid jid, string name = null, params string[] groups) + public void AddContact(Jid jid, string? name = null, params string[] groups) { AssertValid(); - jid.ThrowIfNull("jid"); + jid.ThrowIfNull(nameof(jid)); // Create a roster item for the new contact. - im.AddToRoster(new RosterItem(jid, name, groups)); + Im.AddToRoster(new RosterItem(jid, name, groups)); // Request a subscription from the contact. - im.RequestSubscription(jid); + Im.RequestSubscription(jid); } /// @@ -1136,10 +859,10 @@ public void AddContact(Jid jid, string name = null, params string[] groups) public void RemoveContact(Jid jid) { AssertValid(); - jid.ThrowIfNull("jid"); + jid.ThrowIfNull(nameof(jid)); // This removes the contact from the user's roster AND also cancels any // subscriptions. - im.RemoveFromRoster(jid); + Im.RemoveFromRoster(jid); } /// @@ -1162,8 +885,8 @@ public void RemoveContact(Jid jid) public void RemoveContact(RosterItem item) { AssertValid(); - item.ThrowIfNull("item"); - im.RemoveFromRoster(item); + item.ThrowIfNull(nameof(item)); + Im.RemoveFromRoster(item); } #if WINDOWSPLATFORM @@ -1205,11 +928,11 @@ public void RemoveContact(RosterItem item) /// The following file types are supported: /// BMP, GIF, JPEG, PNG and TIFF. /// - public void SetAvatar(string filePath) { - AssertValid(); - filePath.ThrowIfNull("filePath"); - userAvatar.Publish(filePath); - } + public void SetAvatar(string filePath) { + AssertValid(); + filePath.ThrowIfNull(nameof(filePath)); + userAvatar.Publish(filePath); + } #endif /// @@ -1219,14 +942,12 @@ public void SetAvatar(string filePath) { public void SetvCardAvatar(string filePath) { AssertValid(); - filePath.ThrowIfNull("filePath"); + filePath.ThrowIfNull(nameof(filePath)); try { - using (Stream s = File.OpenRead(filePath)) - { - vcardAvatars.SetAvatar(s); - } + using Stream s = File.OpenRead(filePath); + vcardAvatars.SetAvatar(s); } catch (IOException copyError) { @@ -1254,7 +975,7 @@ public void GetvCardAvatar(string jid, string filepath, Action call /// The payload string to provide to the Request /// The callback method to call after the Request Result has being received. Included the serialised dat /// of the answer to the request - public void RequestCustomIq(Jid jid, string str, Action callback = null) + public void RequestCustomIq(Jid jid, string str, Action? callback = null) { AssertValid(); if (callback == null) cusiqextension.RequestCustomIq(jid, str); @@ -1274,10 +995,10 @@ public void RequestCustomIq(Jid jid, string str, Action callback = null) /// the XMPP server. /// The XmppClient object has been /// disposed. - public void SetMood(Mood mood, string description = null) + public void SetMood(Mood mood, string? description = null) { AssertValid(); - userMood.SetMood(mood, description); + userMood?.SetMood(mood, description); } /// @@ -1296,7 +1017,7 @@ public void SetMood(Mood mood, string description = null) /// disposed. /// public void SetActivity(GeneralActivity activity, SpecificActivity specific = - SpecificActivity.Other, string description = null) + SpecificActivity.Other, string? description = null) { AssertValid(); userActivity.SetActivity(activity, specific, description); @@ -1332,8 +1053,8 @@ public void SetActivity(GeneralActivity activity, SpecificActivity specific = /// disposed. /// Publishing no information (i.e. calling Publish without any parameters /// is considered a "stop command" to disable publishing). - public void SetTune(string title = null, string artist = null, string track = null, - int length = 0, int rating = 0, string source = null, string uri = null) + public void SetTune(string? title = null, string? artist = null, string? track = null, + int length = 0, int rating = 0, string? source = null, string? uri = null) { AssertValid(); userTune.Publish(title, artist, track, length, rating, source, uri); @@ -1369,34 +1090,20 @@ public void SetTune(TuneInformation tune) /// A callback method to invoke when a request for a file-transfer is received /// from another XMPP user. /// - public FileTransferRequest FileTransferRequest + public FileTransferRequest? FileTransferRequest { - get - { - return siFileTransfer.TransferRequest; - } - - set - { - siFileTransfer.TransferRequest = value; - } + get => siFileTransfer.TransferRequest; + set => siFileTransfer.TransferRequest = value; } /// /// A callback method to invoke when a Custom Iq Request is received /// from another XMPP user. /// - public CustomIqRequestDelegate CustomIqDelegate + public CustomIqRequestDelegate? CustomIqDelegate { - get - { - return im.CustomIqDelegate; - } - - set - { - im.CustomIqDelegate = value; - } + get => Im.CustomIqDelegate; + set => Im.CustomIqDelegate = value; } /// @@ -1405,10 +1112,10 @@ public CustomIqRequestDelegate CustomIqDelegate /// /// The JID of the XMPP user to offer the file to. /// The path of the file to transfer. - /// a callback method invoked once the other site has - /// accepted or rejected the file-transfer request. /// A description of the file so the receiver can /// better understand what is being sent. + /// a callback method invoked once the other site has + /// accepted or rejected the file-transfer request. /// Sid of the file transfer /// The to parameter or the path /// parameter is null. @@ -1439,7 +1146,7 @@ public CustomIqRequestDelegate CustomIqDelegate /// The XmppClient object has been /// disposed. public string InitiateFileTransfer(Jid to, string path, - string description = null, Action cb = null) + string? description = null, Action? cb = null) { AssertValid(); return siFileTransfer.InitiateFileTransfer(to, path, description, cb); @@ -1455,10 +1162,10 @@ public string InitiateFileTransfer(Jid to, string path, /// The name of the file, as offered to the XMPP user /// with the specified JID. /// The number of bytes to transfer. - /// A callback method invoked once the other site has - /// accepted or rejected the file-transfer request. /// A description of the file so the receiver can /// better understand what is being sent. + /// A callback method invoked once the other site has + /// accepted or rejected the file-transfer request. /// The Sid of the file transfer /// The to parameter or the stream /// parameter or the name parameter is null. @@ -1479,7 +1186,7 @@ public string InitiateFileTransfer(Jid to, string path, /// The XmppClient object has been /// disposed. public string InitiateFileTransfer(Jid to, Stream stream, string name, long size, - string description = null, Action cb = null) + string? description = null, Action? cb = null) { AssertValid(); return siFileTransfer.InitiateFileTransfer(to, stream, name, size, description, cb); @@ -1501,15 +1208,15 @@ public string InitiateFileTransfer(Jid to, Stream stream, string name, long size public void CancelFileTransfer(FileTransfer transfer) { AssertValid(); - transfer.ThrowIfNull("transfer"); + transfer.ThrowIfNull(nameof(transfer)); siFileTransfer.CancelFileTransfer(transfer); } /// /// Cancels the specified file-transfer. /// - /// From Jid /// Sid + /// From Jid /// To Jid /// The transfer parameter is /// null. @@ -1528,8 +1235,8 @@ public void CancelFileTransfer(string sid, Jid from, Jid to) AssertValid(); sid.ThrowIfNullOrEmpty("sid"); - from.ThrowIfNull("from"); - to.ThrowIfNull("to"); + from.ThrowIfNull(nameof(from)); + to.ThrowIfNull(nameof(to)); siFileTransfer.CancelFileTransfer(sid, from, to); } @@ -1556,7 +1263,7 @@ public void CancelFileTransfer(string sid, Jid from, Jid to) /// public void Register(RegistrationCallback callback) { - callback.ThrowIfNull("callback"); + callback.ThrowIfNull(nameof(callback)); inBandRegistration.Register(callback); } @@ -1569,7 +1276,7 @@ public void Register(RegistrationCallback callback) /// The jid parameter is null. /// The XmppClient instance is /// not connected to a remote host. - /// There was a failure while writing to or + /// There was a failure while writing to or /// reading from the network. /// The XMPP client of the /// user with the specified JID does not support the retrieval of the @@ -1600,7 +1307,7 @@ public DateTime GetTime(Jid jid) /// The XmppClient instance is not /// connected to a remote host, or the XmppCleint instance has not authenticated /// with the XMPP server. - /// There was a failure while writing to or + /// There was a failure while writing to or /// reading from the network. /// The XMPP client of the /// user with the specified JID does not support the retrieval of version @@ -1630,7 +1337,7 @@ public VersionInformation GetVersion(Jid jid) /// The jid parameter is null. /// The XmppClient instance is /// not connected to a remote host. - /// There was a failure while writing to or + /// There was a failure while writing to or /// reading from the network. /// The XMPP client of the /// user with the specified JID does not support the retrieval of feature @@ -1691,7 +1398,7 @@ public IEnumerable GetItems(Jid jid) /// The jid parameter is null. /// The XmppClient instance is /// not connected to a remote host. - /// There was a failure while writing to or + /// There was a failure while writing to or /// reading from the network. /// The XMPP client of the /// user with the specified JID does not support buzzing. @@ -1703,7 +1410,7 @@ public IEnumerable GetItems(Jid jid) /// condition. /// The server returned invalid data or another /// unspecified XMPP error occurred. - public void Buzz(Jid jid, string message = null) + public void Buzz(Jid jid, string? message = null) { AssertValid(); attention.GetAttention(jid, message); @@ -1718,7 +1425,7 @@ public void Buzz(Jid jid, string message = null) /// The jid parameter is null. /// The XmppClient instance is /// not connected to a remote host. - /// There was a failure while writing to or + /// There was a failure while writing to or /// reading from the network. /// The XMPP client of the /// user with the specified JID does not support the 'Ping' XMPP protocol @@ -1758,52 +1465,53 @@ public TimeSpan Ping(Jid jid) public void Block(Jid jid) { AssertValid(); - jid.ThrowIfNull("jid"); + jid.ThrowIfNull(nameof(jid)); // If our server supports the 'Blocking Command' extension, we can just // use that. - if (block.Supported) + if (block?.Supported == true) + { block.Block(jid); + } else { // Privacy list blocking. If our server doesn't support privacy lists, we're // out of luck. - PrivacyList privacyList = null; - string name = im.GetDefaultPrivacyList(); + PrivacyList? privacyList = null; + var name = Im.GetDefaultPrivacyList(); if (name != null) - privacyList = im.GetPrivacyList(name); + privacyList = Im.GetPrivacyList(name); // If no default list has been set, look for a 'blocklist' list. - foreach (var list in im.GetPrivacyLists()) + foreach (var list in Im.GetPrivacyLists()) { if (list.Name == "blocklist") privacyList = list; } // If 'blocklist' doesn't exist, create it and set it as default. - if (privacyList == null) - privacyList = new PrivacyList("blocklist"); + privacyList ??= new PrivacyList("blocklist"); privacyList.Add(new JidPrivacyRule(jid, false, 0), true); // Save the privacy list and activate it. - im.EditPrivacyList(privacyList); - im.SetDefaultPrivacyList(privacyList.Name); - im.SetActivePrivacyList(privacyList.Name); + Im.EditPrivacyList(privacyList); + Im.SetDefaultPrivacyList(privacyList.Name); + Im.SetActivePrivacyList(privacyList.Name); } } /// /// Fetch message history from the server. - /// + /// /// The 'start' and 'end' attributes MAY be specified to indicate a date range. - /// + /// /// If the 'with' attribute is omitted then collections with any JID are returned. - /// + /// /// If only 'start' is specified then all collections on or after that date should be returned. - /// + /// /// If only 'end' is specified then all collections prior to that date should be returned. /// /// Paging options /// Optional start date range to query /// Optional enddate range to query /// Optional JID to filter archive results by - public XmppPage GetArchivedChatIds(XmppPageRequest pageRequest, DateTimeOffset? start = null, DateTimeOffset? end = null, Jid with = null) + public XmppPage GetArchivedChatIds(XmppPageRequest pageRequest, DateTimeOffset? start = null, DateTimeOffset? end = null, Jid? with = null) { return messageArchiving.GetArchivedChatIds(pageRequest, start, end, with); } @@ -1826,7 +1534,7 @@ public ArchivedChatPage GetArchivedChat(XmppPageRequest pageRequest, Jid with, D /// Optional filter to only return messages if they match the supplied JID /// Optional filter to only return messages whose timestamp is equal to or later than the given timestamp. /// Optional filter to only return messages whose timestamp is equal to or earlier than the timestamp given in the 'end' field. - public Task> GetArchivedMessages(XmppPageRequest pageRequest, Jid with = null, DateTimeOffset? start = null, DateTimeOffset? end = null) + public Task> GetArchivedMessages(XmppPageRequest pageRequest, Jid? with = null, DateTimeOffset? start = null, DateTimeOffset? end = null) { return messageArchiveManagement.GetArchivedMessages(pageRequest, with, null, start, end); } @@ -1852,7 +1560,7 @@ public ArchivedChatPage GetArchivedChat(XmppPageRequest pageRequest, ArchivedCha { return messageArchiving.GetArchivedChat(pageRequest, chatId); } - + /// /// Unblocks all communication to and from the XMPP entity with the specified /// JID. @@ -1877,21 +1585,23 @@ public ArchivedChatPage GetArchivedChat(XmppPageRequest pageRequest, ArchivedCha public void Unblock(Jid jid) { AssertValid(); - jid.ThrowIfNull("jid"); + jid.ThrowIfNull(nameof(jid)); // If our server supports the 'Blocking Command' extension, we can just // use that. if (block.Supported) + { block.Unblock(jid); + } else { // Privacy list blocking. If our server doesn't support privacy lists, we're // out of luck. - PrivacyList privacyList = null; - string name = im.GetDefaultPrivacyList(); + PrivacyList? privacyList = null; + var name = Im.GetDefaultPrivacyList(); if (name != null) - privacyList = im.GetPrivacyList(name); + privacyList = Im.GetPrivacyList(name); // If no default list has been set, look for a 'blocklist' list. - foreach (var list in im.GetPrivacyLists()) + foreach (var list in Im.GetPrivacyLists()) { if (list.Name == "blocklist") privacyList = list; @@ -1900,27 +1610,23 @@ public void Unblock(Jid jid) if (privacyList == null) return; ISet set = new HashSet(); - foreach (var rule in privacyList) + foreach (var jidRule in privacyList.OfType()) { - if (rule is JidPrivacyRule) - { - var jidRule = rule as JidPrivacyRule; - if (jidRule.Jid == jid && jidRule.Allow == false) - set.Add(jidRule); - } + if (jidRule.Jid == jid && !jidRule.Allow) + set.Add(jidRule); } foreach (var rule in set) privacyList.Remove(rule); // Save the privacy list and activate it. if (privacyList.Count == 0) { - im.SetDefaultPrivacyList(); - im.RemovePrivacyList(privacyList.Name); + Im.SetDefaultPrivacyList(); + Im.RemovePrivacyList(privacyList.Name); } else { - im.EditPrivacyList(privacyList); - im.SetDefaultPrivacyList(privacyList.Name); + Im.EditPrivacyList(privacyList); + Im.SetDefaultPrivacyList(privacyList.Name); } } } @@ -1945,13 +1651,13 @@ public void Unblock(Jid jid) public IEnumerable GetBlocklist() { AssertValid(); - if (block.Supported) + if (block?.Supported == true) return block.GetBlocklist(); - PrivacyList privacyList = null; - string name = im.GetDefaultPrivacyList(); + PrivacyList? privacyList = null; + var name = Im.GetDefaultPrivacyList(); if (name != null) - privacyList = im.GetPrivacyList(name); - foreach (var list in im.GetPrivacyLists()) + privacyList = Im.GetPrivacyList(name); + foreach (var list in Im.GetPrivacyLists()) { if (list.Name == "blocklist") privacyList = list; @@ -1961,8 +1667,8 @@ public IEnumerable GetBlocklist() { foreach (var rule in privacyList) { - if (rule is JidPrivacyRule) - items.Add((rule as JidPrivacyRule).Jid); + if (rule is JidPrivacyRule privacy) + items.Add(privacy.Jid); } } return items; @@ -1979,7 +1685,6 @@ public IEnumerable DiscoverRooms(Jid chatService) return groupChat.DiscoverRooms(chatService); } - /// /// Returns a list of active public chat room messages. /// @@ -1997,7 +1702,7 @@ public RoomInfoExtended GetRoomInfo(Jid chatRoom) /// Chat room /// Desired nickname /// (Optional) Password - public void JoinRoom(Jid chatRoom, string nickname, string password = null) + public void JoinRoom(Jid chatRoom, string nickname, string? password = null) { AssertValid(); groupChat.JoinRoom(chatRoom, nickname, password); @@ -2024,7 +1729,6 @@ public bool SendRegistration(Jid room, DataForm form) return groupChat.SendRegistration(room, form); } - /// /// Request immediate room creation with default server options /// @@ -2051,7 +1755,7 @@ public void LeaveRoom(Jid room, string nickname) /// /// Chat room /// (Optional) Reason to destroy room. - public bool DestroyRoom(Jid room, string reason = null) + public bool DestroyRoom(Jid room, string? reason = null) { AssertValid(); return groupChat.DestroyRoom(room, reason); @@ -2063,7 +1767,7 @@ public bool DestroyRoom(Jid room, string reason = null) /// Chat room /// User to be banned /// (Optional) Reason for the ban. - public bool BanUser(Jid room, Jid user, string reason = null) + public bool BanUser(Jid room, Jid user, string? reason = null) { AssertValid(); return groupChat.SetPrivilege(room, user, Affiliation.Outcast, reason); @@ -2076,7 +1780,7 @@ public bool BanUser(Jid room, Jid user, string reason = null) /// User with admin permission /// (Optional) Desired nickname /// (Optional) Reason - public bool AddAdminToRoom(Jid room, Jid user, string nick = null, string reason = null) + public bool AddAdminToRoom(Jid room, Jid user, string? nick = null, string? reason = null) { AssertValid(); return groupChat.SetPrivilege(room, user, Affiliation.Admin, reason, nick); @@ -2088,7 +1792,7 @@ public bool AddAdminToRoom(Jid room, Jid user, string nick = null, string reason /// Chat room /// User with admin permission /// (Optional) Reason - public bool RemoveUser(Jid room, Jid user, string reason = null) + public bool RemoveUser(Jid room, Jid user, string? reason = null) { AssertValid(); return groupChat.SetPrivilege(room, user, Affiliation.None, reason); @@ -2101,7 +1805,7 @@ public bool RemoveUser(Jid room, Jid user, string reason = null) /// User with admin permission /// (Optional) Desired nickname /// (Optional) Reason - public bool AddMemberToRoom(Jid room, Jid user, string nick = null, string reason = null) + public bool AddMemberToRoom(Jid room, Jid user, string? nick = null, string? reason = null) { AssertValid(); groupChat.SetPrivilege(room, user, Affiliation.Member, reason, nick); @@ -2198,14 +1902,14 @@ public IEnumerable GetRoomModerators(Jid chatRoom) AssertValid(); return groupChat.GetMembers(chatRoom, Role.Moderator); } - + /// /// Allows moderators to kick an occupant from the room. /// /// chat room /// user to kick /// reason for kick - public void KickGroupOccupant(Jid chatRoom, string nickname, string reason = null) + public void KickGroupOccupant(Jid chatRoom, string nickname, string? reason = null) { groupChat.KickOccupant(chatRoom, nickname, reason); } @@ -2225,10 +1929,10 @@ public void ModifyRoomConfig(Jid room, RegistrationCallback callback) /// Asks the chat service to invite the specified user to the chat room you specify. /// /// user you intend to invite to chat room. - /// message you want to send to the user. /// Jid of the chat room. + /// message you want to send to the user. /// Password if any. - public void SendInvite(Jid to, Jid chatRoom, string message, string password = null) + public void SendInvite(Jid to, Jid chatRoom, string message, string? password = null) { groupChat.SendInvite(to, chatRoom, message, password); } @@ -2276,10 +1980,10 @@ public DataForm RequestSearchForm() /// Request the Search Form /// /// DataForm for avaible fields search - public void RequestSlot(string fileName, long size, string contentType, Action upload, Action error) + public void RequestSlot(string fileName, long size, string contentType, Action upload, Action error) { AssertValid(); - this.httpUpload.RequestSlot(fileName, size, contentType, upload, error); + httpUpload.RequestSlot(fileName, size, contentType, upload, error); } /// @@ -2319,9 +2023,8 @@ protected virtual void Dispose(bool disposing) // Get rid of managed resources. if (disposing) { - if (im != null) - im.Close(); - im = null; + Im?.Close(); + Im = null!; } // Get rid of unmanaged resources. } @@ -2348,43 +2051,44 @@ private void AssertValid() /// /// Initializes the various XMPP extension modules. /// - private void LoadExtensions() - { - version = im.LoadExtension(); - sdisco = im.LoadExtension(); - ecapa = im.LoadExtension(); - ping = im.LoadExtension(); - attention = im.LoadExtension(); - time = im.LoadExtension(); - block = im.LoadExtension(); - pep = im.LoadExtension(); - userTune = im.LoadExtension(); + + public XmppClient(XmppIm xmppIm) + { + Im = xmppIm; + version = Im.LoadExtension(); + sdisco = Im.LoadExtension(); + ecapa = Im.LoadExtension(); + ping = Im.LoadExtension(); + attention = Im.LoadExtension(); + time = Im.LoadExtension(); + block = Im.LoadExtension(); + pep = Im.LoadExtension(); + userTune = Im.LoadExtension(); #if WINDOWSPLATFORM - userAvatar = im.LoadExtension(); + userAvatar = Im.LoadExtension(); #endif - userMood = im.LoadExtension(); - dataForms = im.LoadExtension(); - featureNegotiation = im.LoadExtension(); - streamInitiation = im.LoadExtension(); - siFileTransfer = im.LoadExtension(); - inBandBytestreams = im.LoadExtension(); - userActivity = im.LoadExtension(); - socks5Bytestreams = im.LoadExtension(); - FileTransferSettings = new FileTransferSettings(socks5Bytestreams, - siFileTransfer); - serverIpCheck = im.LoadExtension(); - messageCarbons = im.LoadExtension(); - inBandRegistration = im.LoadExtension(); - chatStateNotifications = im.LoadExtension(); - bitsOfBinary = im.LoadExtension(); - vcardAvatars = im.LoadExtension(); - vcard = im.LoadExtension(); - cusiqextension = im.LoadExtension(); - groupChat = im.LoadExtension(); - search = im.LoadExtension(); - messageArchiving = im.LoadExtension(); - messageArchiveManagement = im.LoadExtension(); - httpUpload = im.LoadExtension(); + userMood = Im.LoadExtension(); + dataForms = Im.LoadExtension(); + featureNegotiation = Im.LoadExtension(); + streamInitiation = Im.LoadExtension(); + siFileTransfer = Im.LoadExtension(); + inBandBytestreams = Im.LoadExtension(); + userActivity = Im.LoadExtension(); + socks5Bytestreams = Im.LoadExtension(); + FileTransferSettings = new FileTransferSettings(socks5Bytestreams, siFileTransfer); + serverIpCheck = Im.LoadExtension(); + messageCarbons = Im.LoadExtension(); + inBandRegistration = Im.LoadExtension(); + chatStateNotifications = Im.LoadExtension(); + bitsOfBinary = Im.LoadExtension(); + vcardAvatars = Im.LoadExtension(); + vcard = Im.LoadExtension(); + cusiqextension = Im.LoadExtension(); + groupChat = Im.LoadExtension(); + search = Im.LoadExtension(); + messageArchiving = Im.LoadExtension(); + messageArchiveManagement = Im.LoadExtension(); + httpUpload = Im.LoadExtension(); } } } \ No newline at end of file diff --git a/Core/ConnectEventArgs.cs b/Core/ConnectEventArgs.cs index 8f96bd5e..78e70c11 100644 --- a/Core/ConnectEventArgs.cs +++ b/Core/ConnectEventArgs.cs @@ -1,16 +1,11 @@ using System; - namespace Net.Xmpp.Core { public enum ConnectionState { Connected, Disconnected, Lost } public class ConnectEventArgs : EventArgs { - public ConnectionState State - { - get; - private set; - } + public ConnectionState State { get; } public ConnectEventArgs(ConnectionState state) { diff --git a/Core/ErrorEventArgs.cs b/Core/ErrorEventArgs.cs index 480f5f98..1356a961 100644 --- a/Core/ErrorEventArgs.cs +++ b/Core/ErrorEventArgs.cs @@ -10,22 +10,12 @@ public class ErrorEventArgs : EventArgs /// /// The reason why the error event was raised. /// - public string Reason - { - get - { - return Exception.Message; - } - } + public string Reason => Exception.Message; /// /// The exception that caused the error event. /// - public Exception Exception - { - get; - private set; - } + public Exception Exception { get; } /// /// Initializes a new instance of the ErrorEventArgs class. @@ -34,7 +24,7 @@ public Exception Exception /// The e parameter is null. public ErrorEventArgs(Exception e) { - e.ThrowIfNull("e"); + e.ThrowIfNull(nameof(e)); Exception = e; } } diff --git a/Core/Iq.cs b/Core/Iq.cs index 0395f237..33eb48a8 100644 --- a/Core/Iq.cs +++ b/Core/Iq.cs @@ -16,10 +16,7 @@ public class Iq : Stanza /// public IqType Type { - get - { - return ParseType(element.GetAttribute("type")); - } + get => ParseType(element.GetAttribute("type")); set { @@ -36,20 +33,14 @@ public bool IsRequest get { var t = Type; - return t == IqType.Set || t == IqType.Get; + return t is IqType.Set or IqType.Get; } } /// /// Determines whether the IQ stanza is a response. /// - public bool IsResponse - { - get - { - return !IsRequest; - } - } + public bool IsResponse => !IsRequest; /// /// Initializes a new instance of the Iq class. @@ -61,9 +52,9 @@ public bool IsResponse /// The content of the stanza. /// The language of the XML character data of /// the stanza. - public Iq(IqType type, string id, Jid to = null, Jid from = null, - XmlElement data = null, CultureInfo language = null) - : base(null, to, from, id, language, data) + public Iq(IqType type, string? id, Jid? to = null, Jid? from = null, + XmlElement? data = null, CultureInfo? language = null) + : base(null, to, from, id, language, data is null ? new XmlElement[] { } : new[] { data }) { Type = type; } @@ -90,13 +81,13 @@ public Iq(XmlElement element) /// private IqType ParseType(string value) { - value.ThrowIfNull("value"); + value.ThrowIfNull(nameof(value)); var dict = new Dictionary() { - { "set", IqType.Set }, - { "get", IqType.Get }, - { "result", IqType.Result }, - { "error", IqType.Error } - }; + { "set", IqType.Set }, + { "get", IqType.Get }, + { "result", IqType.Result }, + { "error", IqType.Error } + }; return dict[value]; } diff --git a/Core/IqEventArgs.cs b/Core/IqEventArgs.cs index c538de09..3fb31055 100644 --- a/Core/IqEventArgs.cs +++ b/Core/IqEventArgs.cs @@ -10,11 +10,7 @@ public class IqEventArgs : EventArgs /// /// The IQ stanza. /// - public Iq Stanza - { - get; - private set; - } + public Iq Stanza { get; } /// /// Initializes a new instance of the IqEventArgs class. @@ -24,7 +20,7 @@ public Iq Stanza /// The stanza parameter is null. public IqEventArgs(Iq stanza) { - stanza.ThrowIfNull("stanza"); + stanza.ThrowIfNull(nameof(stanza)); Stanza = stanza; } } diff --git a/Core/Message.cs b/Core/Message.cs index ad135ccc..c815d0b2 100644 --- a/Core/Message.cs +++ b/Core/Message.cs @@ -18,9 +18,9 @@ public class Message : Stanza /// The ID of the Message stanza. /// The language of the XML character data of /// the stanza. - public Message(Jid to = null, Jid from = null, XmlElement data = null, - string id = null, CultureInfo language = null) - : base(null, to, from, id, language, data) + public Message(Jid? to = null, Jid? from = null, XmlElement? data = null, + string? id = null, CultureInfo? language = null) + : base(null, to, from, id, language, data is null ? new XmlElement[] { } : new[] { data }) { } diff --git a/Core/MessageEventArgs.cs b/Core/MessageEventArgs.cs index 87d5ad75..cd74df71 100644 --- a/Core/MessageEventArgs.cs +++ b/Core/MessageEventArgs.cs @@ -10,11 +10,7 @@ public class MessageEventArgs : EventArgs /// /// The Message stanza. /// - public Message Stanza - { - get; - private set; - } + public Message Stanza { get; } /// /// Initializes a new instance of the MessageEventArgs class. @@ -25,7 +21,7 @@ public Message Stanza /// is null. public MessageEventArgs(Message stanza) { - stanza.ThrowIfNull("stanza"); + stanza.ThrowIfNull(nameof(stanza)); Stanza = stanza; } } diff --git a/Core/Presence.cs b/Core/Presence.cs index 09b21fdc..3e993b1f 100644 --- a/Core/Presence.cs +++ b/Core/Presence.cs @@ -14,12 +14,12 @@ public class Presence : Stanza /// /// The JID of the intended recipient for the stanza. /// The JID of the sender. - /// The content of the stanza. /// The ID of the Presence stanza. /// The language of the XML character data of /// the stanza. - public Presence(Jid to = null, Jid from = null, string id = null, - CultureInfo language = null, params XmlElement[] data) + /// The content of the stanza. + public Presence(Jid? to = null, Jid? from = null, string? id = null, + CultureInfo? language = null, params XmlElement[]? data) : base(null, to, from, id, language, data) { } diff --git a/Core/PresenceEventArgs.cs b/Core/PresenceEventArgs.cs index 3d0b167b..8cc9d04f 100644 --- a/Core/PresenceEventArgs.cs +++ b/Core/PresenceEventArgs.cs @@ -10,11 +10,7 @@ public class PresenceEventArgs : EventArgs /// /// The Presence stanza. /// - public Presence Stanza - { - get; - private set; - } + public Presence Stanza { get; } /// /// Initializes a new instance of the PresenceEventArgs class. @@ -25,7 +21,7 @@ public Presence Stanza /// null. public PresenceEventArgs(Presence stanza) { - stanza.ThrowIfNull("stanza"); + stanza.ThrowIfNull(nameof(stanza)); Stanza = stanza; } } diff --git a/Core/Sasl/Mechanisms/SaslDigestMd5.cs b/Core/Sasl/Mechanisms/SaslDigestMd5.cs index 1168fa6f..3abca63f 100644 --- a/Core/Sasl/Mechanisms/SaslDigestMd5.cs +++ b/Core/Sasl/Mechanisms/SaslDigestMd5.cs @@ -16,7 +16,7 @@ internal class SaslDigestMd5 : SaslMechanism /// /// The client nonce value used during authentication. /// - private string Cnonce = GenerateCnonce(); + private readonly string Cnonce = GenerateCnonce(); /// /// Cram-Md5 involves several steps. @@ -27,52 +27,29 @@ internal class SaslDigestMd5 : SaslMechanism /// True if the authentication exchange between client and server /// has been completed. /// - public override bool IsCompleted - { - get - { - return Completed; - } - } + public override bool IsCompleted => Completed; /// /// The server sends the first message in the authentication exchange. /// - public override bool HasInitial - { - get - { - return false; - } - } + public override bool HasInitial => false; /// /// The IANA name for the Digest-Md5 authentication mechanism as described /// in RFC 2195. /// - public override string Name - { - get - { - return "DIGEST-MD5"; - } - } + public override string Name => "DIGEST-MD5"; /// /// The username to authenticate with. /// private string Username { - get - { - return Properties.ContainsKey("Username") ? - Properties["Username"] as string : null; - } + get => Properties.ContainsKey(nameof(Username)) + ? (string)Properties[nameof(Username)] + : throw new ArgumentNullException(nameof(Username)); - set - { - Properties["Username"] = value; - } + set => Properties[nameof(Username)] = value; } /// @@ -80,16 +57,11 @@ private string Username /// private string Password { - get - { - return Properties.ContainsKey("Password") ? - Properties["Password"] as string : null; - } + get => Properties.ContainsKey(nameof(Password)) + ? (string)Properties[nameof(Password)] + : throw new ArgumentNullException(nameof(Password)); - set - { - Properties["Password"] = value; - } + set => Properties[nameof(Password)] = value; } /// @@ -130,10 +102,10 @@ internal SaslDigestMd5(string username, string password, string cnonce) /// the empty string. public SaslDigestMd5(string username, string password) { - username.ThrowIfNull("username"); - if (username == String.Empty) + username.ThrowIfNull(nameof(username)); + if (username.Length == 0) throw new ArgumentException("The username must not be empty."); - password.ThrowIfNull("password"); + password.ThrowIfNull(nameof(password)); Username = username; Password = password; @@ -155,7 +127,7 @@ protected override byte[] ComputeResponse(byte[] challenge) // with a CRLF. byte[] ret = Step == 0 ? ComputeDigestResponse(challenge) : new byte[0]; - Step = Step + 1; + Step++; return ret; } @@ -163,7 +135,7 @@ private byte[] ComputeDigestResponse(byte[] challenge) { // Precondition: Ensure username and password are not null and // username is not empty. - if (String.IsNullOrEmpty(Username) || Password == null) + if (!(Username?.Length > 0) || Password == null) { throw new SaslException("The username must not be null or empty and " + "the password must not be null."); @@ -177,18 +149,18 @@ private byte[] ComputeDigestResponse(byte[] challenge) // Create the challenge-response string. string[] directives = new string[] { - // We don't use UTF-8 in the current implementation. - //"charset=utf-8", - "username=" + UsernameBackslashEscapeXep106(Dquote(Username)), - "realm=" + Dquote(fields["realm"]), - "nonce="+ Dquote(fields["nonce"]), - "nc=00000001", - "cnonce=" + Dquote(Cnonce), - "digest-uri=" + Dquote(digestUri), - "response=" + responseValue, - "qop=" + fields["qop"] - }; - string challengeResponse = String.Join(",", directives); + // We don't use UTF-8 in the current implementation. + //"charset=utf-8", + "username=" + UsernameBackslashEscapeXep106(Dquote(Username)), + "realm=" + Dquote(fields["realm"]), + "nonce="+ Dquote(fields["nonce"]), + "nc=00000001", + "cnonce=" + Dquote(Cnonce), + "digest-uri=" + Dquote(digestUri), + "response=" + responseValue, + "qop=" + fields["qop"] + }; + string challengeResponse = string.Join(",", directives); // Finally, return the response as a byte array. return Encoding.ASCII.GetBytes(challengeResponse); } @@ -207,10 +179,9 @@ private byte[] ComputeDigestResponse(byte[] challenge) /// format of the challenge sent by the server. private static NameValueCollection ParseDigestChallenge(string challenge) { - challenge.ThrowIfNull("challenge"); - NameValueCollection coll = new NameValueCollection(); - string[] parts = challenge.Split(','); - foreach (string p in parts) + challenge.ThrowIfNull(nameof(challenge)); + NameValueCollection coll = new(); + foreach (string p in challenge.Split(',')) { string[] kv = p.Split(new char[] { '=' }, 2); if (kv.Length == 2) @@ -245,20 +216,18 @@ private static string ComputeDigestResponseValue(NameValueCollection challenge, Encoding enc = Encoding.GetEncoding("ISO-8859-1"); string ncValue = "00000001", realm = challenge["realm"]; // Construct A1. - using (var md5p = new MD5CryptoServiceProvider()) - { - byte[] data = enc.GetBytes(username + ":" + realm + ":" + password); - data = md5p.ComputeHash(data); - string A1 = enc.GetString(data) + ":" + challenge["nonce"] + ":" + - cnonce; - // Construct A2. - string A2 = "AUTHENTICATE:" + digestUri; - if (!"auth".Equals(challenge["qop"])) - A2 = A2 + ":00000000000000000000000000000000"; - string ret = MD5(A1, enc) + ":" + challenge["nonce"] + ":" + ncValue + - ":" + cnonce + ":" + challenge["qop"] + ":" + MD5(A2, enc); - return MD5(ret, enc); - } + using var md5p = new MD5CryptoServiceProvider(); + byte[] data = enc.GetBytes(username + ":" + realm + ":" + password); + data = md5p.ComputeHash(data); + string A1 = enc.GetString(data) + ":" + challenge["nonce"] + ":" + + cnonce; + // Construct A2. + string A2 = "AUTHENTICATE:" + digestUri; + if (!"auth".Equals(challenge["qop"])) + A2 += ":00000000000000000000000000000000"; + string ret = MD5(A1, enc) + ":" + challenge["nonce"] + ":" + ncValue + + ":" + cnonce + ":" + challenge["qop"] + ":" + MD5(A2, enc); + return MD5(ret, enc); } /// @@ -271,15 +240,15 @@ private static string ComputeDigestResponseValue(NameValueCollection challenge, /// An MD5 hash as a 32-character hex-string. /// The input string /// is null. - private static string MD5(string s, Encoding encoding = null) + private static string MD5(string s, Encoding? encoding = null) { if (s == null) - throw new ArgumentNullException("s"); + throw new ArgumentNullException(nameof(s)); if (encoding == null) encoding = Encoding.UTF8; byte[] data = encoding.GetBytes(s); - byte[] hash = (new MD5CryptoServiceProvider()).ComputeHash(data); - StringBuilder builder = new StringBuilder(); + byte[] hash = new MD5CryptoServiceProvider().ComputeHash(data); + StringBuilder builder = new(); foreach (byte h in hash) builder.Append(h.ToString("x2")); return builder.ToString(); diff --git a/Core/Sasl/Mechanisms/SaslPlain.cs b/Core/Sasl/Mechanisms/SaslPlain.cs index e30fbd0c..e60cefc1 100644 --- a/Core/Sasl/Mechanisms/SaslPlain.cs +++ b/Core/Sasl/Mechanisms/SaslPlain.cs @@ -15,69 +15,41 @@ internal class SaslPlain : SaslMechanism /// True if the authentication exchange between client and server /// has been completed. /// - public override bool IsCompleted - { - get - { - return Completed; - } - } + public override bool IsCompleted => Completed; /// /// Sasl Plain just sends one initial response. /// - public override bool HasInitial - { - get - { - return true; - } - } + public override bool HasInitial => true; /// /// The IANA name for the Plain authentication mechanism as described /// in RFC 4616. /// - public override string Name - { - get - { - return "PLAIN"; - } - } + public override string Name => "PLAIN"; /// /// The username to authenticate with. /// private string Username { - get - { - return Properties.ContainsKey("Username") ? - Properties["Username"] as string : null; - } + get => Properties.ContainsKey(nameof(Username)) + ? (string)Properties[nameof(Username)] + : throw new ArgumentNullException(nameof(Username)); - set - { - Properties["Username"] = value; - } + set => Properties[nameof(Username)] = value; } /// - /// The plain-text password to authenticate with. + /// The password to authenticate with. /// private string Password { - get - { - return Properties.ContainsKey("Password") ? - Properties["Password"] as string : null; - } + get => Properties.ContainsKey(nameof(Password)) + ? (string)Properties[nameof(Password)] + : throw new ArgumentNullException(nameof(Password)); - set - { - Properties["Password"] = value; - } + set => Properties[nameof(Password)] = value; } /// @@ -101,10 +73,10 @@ private SaslPlain() /// is empty. public SaslPlain(string username, string password) { - username.ThrowIfNull("username"); - if (username == String.Empty) + username.ThrowIfNull(nameof(username)); + if (username.Length == 0) throw new ArgumentException("The username must not be empty."); - password.ThrowIfNull("password"); + password.ThrowIfNull(nameof(password)); Username = username; Password = password; @@ -122,7 +94,7 @@ protected override byte[] ComputeResponse(byte[] challenge) { // Precondition: Ensure username and password are not null and // username is not empty. - if (String.IsNullOrEmpty(Username) || Password == null) + if (!(Username?.Length > 0) || Password == null) { throw new SaslException("The username must not be null or empty and " + "the password must not be null."); diff --git a/Core/Sasl/Mechanisms/SaslScramSha1.cs b/Core/Sasl/Mechanisms/SaslScramSha1.cs index 3e1d4474..089d1283 100644 --- a/Core/Sasl/Mechanisms/SaslScramSha1.cs +++ b/Core/Sasl/Mechanisms/SaslScramSha1.cs @@ -17,7 +17,7 @@ internal class SaslScramSha1 : SaslMechanism /// /// The client nonce value used during authentication. /// - private string Cnonce = GenerateCnonce(); + private readonly string Cnonce = GenerateCnonce(); /// /// Scram-Sha-1 involves several steps. @@ -28,64 +28,41 @@ internal class SaslScramSha1 : SaslMechanism /// The salted password. This is needed for client authentication and later /// on again for verifying the server signature. /// - private byte[] SaltedPassword; + private byte[]? SaltedPassword; /// /// The auth message is part of the authentication exchange and is needed for /// authentication as well as for verifying the server signature. /// - private string AuthMessage; + private string? AuthMessage; /// /// True if the authentication exchange between client and server /// has been completed. /// - public override bool IsCompleted - { - get - { - return Completed; - } - } + public override bool IsCompleted => Completed; /// /// Client sends the first message in the authentication exchange. /// - public override bool HasInitial - { - get - { - return true; - } - } + public override bool HasInitial => true; /// /// The IANA name for the Scram-Sha-1 authentication mechanism as described /// in RFC 5802. /// - public override string Name - { - get - { - return "SCRAM-SHA-1"; - } - } + public override string Name => "SCRAM-SHA-1"; /// /// The username to authenticate with. /// private string Username { - get - { - return Properties.ContainsKey("Username") ? - Properties["Username"] as string : null; - } + get => Properties.ContainsKey(nameof(Username)) + ? (string)Properties[nameof(Username)] + : throw new ArgumentNullException(nameof(Username)); - set - { - Properties["Username"] = value; - } + set => Properties[nameof(Username)] = value; } /// @@ -93,16 +70,11 @@ private string Username /// private string Password { - get - { - return Properties.ContainsKey("Password") ? - Properties["Password"] as string : null; - } + get => Properties.ContainsKey(nameof(Password)) + ? (string)Properties[nameof(Password)] + : throw new ArgumentNullException(nameof(Password)); - set - { - Properties["Password"] = value; - } + set => Properties[nameof(Password)] = value; } /// @@ -143,10 +115,10 @@ internal SaslScramSha1(string username, string password, string cnonce) /// the empty string. public SaslScramSha1(string username, string password) { - username.ThrowIfNull("username"); - if (username == String.Empty) + username.ThrowIfNull(nameof(username)); + if (username.Length == 0) throw new ArgumentException("The username must not be empty."); - password.ThrowIfNull("password"); + password.ThrowIfNull(nameof(password)); Username = username; Password = password; @@ -163,7 +135,7 @@ protected override byte[] ComputeResponse(byte[] challenge) { // Precondition: Ensure username and password are not null and // username is not empty. - if (String.IsNullOrEmpty(Username) || Password == null) + if (!(Username?.Length > 0) || Password == null) { throw new SaslException("The username must not be null or empty and " + "the password must not be null."); @@ -173,7 +145,7 @@ protected override byte[] ComputeResponse(byte[] challenge) byte[] ret = Step == 0 ? ComputeInitialResponse() : (Step == 1 ? ComputeFinalResponse(challenge) : VerifyServerSignature(challenge)); - Step = Step + 1; + Step++; return ret; } @@ -185,8 +157,7 @@ protected override byte[] ComputeResponse(byte[] challenge) private byte[] ComputeInitialResponse() { // We don't support channel binding. - return Encoding.UTF8.GetBytes("n,,n=" + SaslPrep(Username) + ",r=" + - Cnonce); + return Encoding.UTF8.GetBytes($"n,,n={SaslPrep(Username)},r={Cnonce}"); } /// @@ -202,7 +173,7 @@ private byte[] ComputeFinalResponse(byte[] challenge) NameValueCollection nv = ParseServerFirstMessage(challenge); // Extract the server data needed to calculate the client proof. string salt = nv["s"], nonce = nv["r"]; - int iterationCount = Int32.Parse(nv["i"]); + int iterationCount = int.Parse(nv["i"]); if (!VerifyServerNonce(nonce)) throw new SaslException("Invalid server nonce: " + nonce); // Calculate the client proof (refer to RFC 5802, p.7). @@ -247,21 +218,24 @@ private bool VerifyServerNonce(string nonce) /// token. private byte[] VerifyServerSignature(byte[] challenge) { + var fail = Encoding.UTF8.GetBytes("*"); string s = Encoding.UTF8.GetString(challenge); // The server must respond with a "v=signature" message. if (!s.StartsWith("v=")) { // Cancel authentication process. - return Encoding.UTF8.GetBytes("*"); + return fail; } byte[] serverSignature = Convert.FromBase64String(s.Substring(2)); + if (SaltedPassword is null || AuthMessage is null) + return fail; // Verify server's signature. byte[] serverKey = HMAC(SaltedPassword, "Server Key"), calculatedSignature = HMAC(serverKey, AuthMessage); // If both signatures are equal, server has been authenticated. Otherwise // cancel the authentication process. return serverSignature.SequenceEqual(calculatedSignature) ? - new byte[0] : Encoding.UTF8.GetBytes("*"); + new byte[0] : fail; } /// @@ -274,9 +248,9 @@ private byte[] VerifyServerSignature(byte[] challenge) /// is null. private NameValueCollection ParseServerFirstMessage(byte[] challenge) { - challenge.ThrowIfNull("challenge"); + challenge.ThrowIfNull(nameof(challenge)); string message = Encoding.UTF8.GetString(challenge); - NameValueCollection coll = new NameValueCollection(); + NameValueCollection coll = new(); foreach (string s in message.Split(',')) { int delimiter = s.IndexOf('='); @@ -301,15 +275,13 @@ private NameValueCollection ParseServerFirstMessage(byte[] challenge) /// Hi is, essentially, PBKDF2 with HMAC as the /// pseudorandom function (PRF) and with dkLen == output length of /// HMAC() == output length of H(). (Refer to RFC 5802, p.6) - private byte[] Hi(string password, string salt, int count) + private byte[] Hi(string? password, string salt, int count) { // The salt is sent by the server as a base64-encoded string. byte[] saltBytes = Convert.FromBase64String(salt); - using (var db = new Rfc2898DeriveBytes(password, saltBytes, count)) - { - // Generate 20 key bytes, which is the size of the hash result of SHA-1. - return db.GetBytes(20); - } + using var db = new Rfc2898DeriveBytes(password, saltBytes, count); + // Generate 20 key bytes, which is the size of the hash result of SHA-1. + return db.GetBytes(20); } /// @@ -322,10 +294,8 @@ private byte[] Hi(string password, string salt, int count) /// The hashcode of the specified data input. private byte[] HMAC(byte[] key, byte[] data) { - using (var hmac = new HMACSHA1(key)) - { - return hmac.ComputeHash(data); - } + using var hmac = new HMACSHA1(key); + return hmac.ComputeHash(data); } /// @@ -349,10 +319,8 @@ private byte[] HMAC(byte[] key, string data) /// The hash value for the specified byte array. private byte[] H(byte[] data) { - using (var sha1 = new SHA1Managed()) - { - return sha1.ComputeHash(data); - } + using var sha1 = new SHA1Managed(); + return sha1.ComputeHash(data); } /// @@ -369,8 +337,8 @@ private byte[] H(byte[] data) /// are not of the same length. private byte[] Xor(byte[] a, byte[] b) { - a.ThrowIfNull("a"); - b.ThrowIfNull("b"); + a.ThrowIfNull(nameof(a)); + b.ThrowIfNull(nameof(b)); if (a.Length != b.Length) throw new ArgumentException(); byte[] ret = new byte[a.Length]; diff --git a/Core/Sasl/SaslException.cs b/Core/Sasl/SaslException.cs index 83ba4dde..35be3b69 100644 --- a/Core/Sasl/SaslException.cs +++ b/Core/Sasl/SaslException.cs @@ -40,7 +40,7 @@ public SaslException(string message, Exception inner) : base(message, inner) { } /// being thrown. /// An object that contains contextual information about the source /// or destination. - protected SaslException(System.Runtime.Serialization.SerializationInfo info, StreamingContext context) + protected SaslException(SerializationInfo info, StreamingContext context) : base(info, context) { } } } \ No newline at end of file diff --git a/Core/Sasl/SaslFactory.cs b/Core/Sasl/SaslFactory.cs index 5f403e9b..32580b9a 100644 --- a/Core/Sasl/SaslFactory.cs +++ b/Core/Sasl/SaslFactory.cs @@ -14,7 +14,6 @@ internal static class SaslFactory private static Dictionary Mechanisms { get; - set; } /// @@ -29,15 +28,14 @@ private static Dictionary Mechanisms /// specified name is not registered with Sasl.SaslFactory. public static SaslMechanism Create(string name) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); if (!Mechanisms.ContainsKey(name)) { throw new SaslException("A Sasl mechanism with the specified name " + "is not registered with Sasl.SaslFactory."); } Type t = Mechanisms[name]; - object o = Activator.CreateInstance(t, true); - return o as SaslMechanism; + return (SaslMechanism)Activator.CreateInstance(t, true); } /// @@ -56,8 +54,8 @@ public static SaslMechanism Create(string name) /// details. public static void Add(string name, Type t) { - name.ThrowIfNull("name"); - t.ThrowIfNull("t"); + name.ThrowIfNull(nameof(name)); + t.ThrowIfNull(nameof(t)); if (!t.IsSubclassOf(typeof(SaslMechanism))) { throw new ArgumentException("The type t must be a subclass " + @@ -83,10 +81,10 @@ static SaslFactory() // Could be moved to App.config to support SASL "plug-in" mechanisms. var list = new Dictionary() { - { "PLAIN", typeof(Sasl.Mechanisms.SaslPlain) }, - { "DIGEST-MD5", typeof(Sasl.Mechanisms.SaslDigestMd5) }, - { "SCRAM-SHA-1", typeof(Sasl.Mechanisms.SaslScramSha1) }, - }; + { "PLAIN", typeof(Mechanisms.SaslPlain) }, + { "DIGEST-MD5", typeof(Mechanisms.SaslDigestMd5) }, + { "SCRAM-SHA-1", typeof(Mechanisms.SaslScramSha1) }, + }; foreach (string key in list.Keys) Mechanisms.Add(key, list[key]); } diff --git a/Core/Sasl/SaslMechanism.cs b/Core/Sasl/SaslMechanism.cs index 0d772a53..accee548 100644 --- a/Core/Sasl/SaslMechanism.cs +++ b/Core/Sasl/SaslMechanism.cs @@ -38,11 +38,7 @@ public abstract bool HasInitial /// A map of mechanism-specific properties which are needed by the /// authentication mechanism to compute it's challenge-responses. /// - public Dictionary Properties - { - get; - private set; - } + public Dictionary Properties { get; } = new(); /// /// Computes the client response to a challenge sent by the server. @@ -51,11 +47,8 @@ public Dictionary Properties /// The client response to the specified challenge. protected abstract byte[] ComputeResponse(byte[] challenge); - /// - /// internal SaslMechanism() { - Properties = new Dictionary(); } /// @@ -77,7 +70,7 @@ public string GetResponse(string challenge) { try { - byte[] data = String.IsNullOrEmpty(challenge) ? new byte[0] : + byte[] data = !(challenge?.Length > 0) ? new byte[0] : Convert.FromBase64String(challenge); byte[] response = ComputeResponse(data); return Convert.ToBase64String(response); diff --git a/Core/Stanza.cs b/Core/Stanza.cs index 19360738..03efca11 100644 --- a/Core/Stanza.cs +++ b/Core/Stanza.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Xml; using System.Xml.Linq; @@ -20,25 +21,21 @@ public abstract class Stanza /// The tag name of the stanza's root element /// Allows the element tag name to be overridden. /// - protected virtual string RootElementName - { - get { return GetType().Name.ToLowerInvariant(); } - } + protected virtual string RootElementName => GetType().Name.ToLowerInvariant(); /// /// Specifies the JID of the intended recipient for the stanza. /// - public Jid To + public Jid? To { get { string v = element.GetAttribute("to"); - return String.IsNullOrEmpty(v) ? null : new Jid(v); + return v?.Length > 0 ? new Jid(v) : null; } - set { - if (value == null) + if (value is null) element.RemoveAttribute("to"); else element.SetAttribute("to", value.ToString()); @@ -49,17 +46,16 @@ public Jid To /// Specifies the JID of the sender. If this is null, the stanza was generated /// by the client's server. /// - public Jid From + public Jid? From { get { string v = element.GetAttribute("from"); - return String.IsNullOrEmpty(v) ? null : new Jid(v); + return v?.Length > 0 ? new Jid(v) : null; } - set { - if (value == null) + if (value is null) element.RemoveAttribute("from"); else element.SetAttribute("from", value.ToString()); @@ -69,17 +65,16 @@ public Jid From /// /// The ID of the stanza, which may be used for internal tracking of stanzas. /// - public string Id + public string? Id { get { var v = element.GetAttribute("id"); - return String.IsNullOrEmpty(v) ? null : v; + return v?.Length > 0 ? v : null; } - set { - if (value == null) + if (value is null) element.RemoveAttribute("id"); else element.SetAttribute("id", value); @@ -90,17 +85,17 @@ public string Id /// The language of the XML character data if the stanza contains data that is /// intended to be presented to a human user. /// - public CultureInfo Language + public CultureInfo? Language { get { string v = element.GetAttribute("xml:lang"); - return String.IsNullOrEmpty(v) ? null : new CultureInfo(v); + return v?.Length > 0 ? new CultureInfo(v) : null; } set { - if (value == null) + if (value is null) element.RemoveAttribute("xml:lang"); else element.SetAttribute("xml:lang", value.Name); @@ -110,24 +105,12 @@ public CultureInfo Language /// /// The data of the stanza. /// - public XmlElement Data - { - get - { - return element; - } - } + public XmlElement Data => element; /// /// Determines whether the stanza is empty, i.e. has no child nodes. /// - public bool IsEmpty - { - get - { - return Data.IsEmpty; - } - } + public bool IsEmpty => Data.IsEmpty; /// /// Initializes a new instance of the Stanza class. @@ -139,16 +122,16 @@ public bool IsEmpty /// The language of the XML character data of /// the stanza. /// The content of the stanza. - public Stanza(string @namespace = null, Jid to = null, - Jid from = null, string id = null, CultureInfo language = null, - params XmlElement[] data) + protected Stanza(string? @namespace = null, Jid? to = null, + Jid? from = null, string? id = null, CultureInfo? language = null, + params XmlElement[]? data) { element = Xml.Element(RootElementName, @namespace); To = to; From = from; Id = id; Language = language; - foreach (XmlElement e in data) + foreach (var e in data ?? Enumerable.Empty()) { if (e != null) element.Child(e); @@ -164,11 +147,11 @@ public Stanza(string @namespace = null, Jid to = null, /// null. protected Stanza(XmlElement element) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); this.element = element; } - /// + /// /// Converts the data XmlElement to an XElement. /// /// The data of the stanza as an XElement. @@ -176,7 +159,7 @@ public XElement DataXElememt() { return XElement.Parse(this.ToString()); } - + /// /// Returns a textual representation of this instance of the Stanza class. /// @@ -191,7 +174,7 @@ public override string ToString() /// /// Tree of element name tags. /// null or with the requested xml element. - protected XmlElement GetNode(params string[] nodeList) + protected XmlElement? GetNode(params string[] nodeList) { return GetNode(element, 0, nodeList); } @@ -203,7 +186,7 @@ protected XmlElement GetNode(params string[] nodeList) /// Current depth in the node list. /// Tree of element name tags. /// null or with the requested xml element. - private XmlElement GetNode(XmlElement node, int depth, params string[] nodeList) + private XmlElement? GetNode(XmlElement node, int depth, params string[] nodeList) { XmlElement child = node[nodeList[depth]]; return child == null || depth == nodeList.Length - 1 ? child : GetNode(child, depth + 1, nodeList); diff --git a/Core/StreamParser.cs b/Core/StreamParser.cs index 408550a7..d1532298 100644 --- a/Core/StreamParser.cs +++ b/Core/StreamParser.cs @@ -15,28 +15,24 @@ internal class StreamParser : IDisposable /// /// The reader that provides the fast-forward access to the XML stream. /// - private XmlReader reader; + private readonly XmlReader reader; /// /// If true, the stream is not closed when the StreamParser instance is /// disposed of. /// - private bool leaveOpen; + private readonly bool leaveOpen; /// /// The stream on which the reader operates. /// - private Stream stream; + private readonly Stream stream; /// /// The default language of any human-readable XML character send over /// that stream. /// - public CultureInfo Language - { - get; - private set; - } + public CultureInfo? Language { get; private set; } /// /// Initializes a new instance of the StreamParser class for the specified @@ -53,7 +49,7 @@ public CultureInfo Language /// XML-stream in it's 'xml:lang' attribute could not be found. public StreamParser(Stream stream, bool leaveOpen = false) { - stream.ThrowIfNull("stream"); + stream.ThrowIfNull(nameof(stream)); this.leaveOpen = leaveOpen; this.stream = stream; reader = XmlReader.Create(stream, new XmlReaderSettings() @@ -84,39 +80,33 @@ public XmlElement NextElement(params string[] expected) { // Advance reader to next node. reader.Read(); - if (reader.NodeType == XmlNodeType.EndElement && reader.Name == - "stream:stream") + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "stream:stream") throw new IOException("The server has closed the XML stream."); if (reader.NodeType != XmlNodeType.Element) - throw new XmlException("Unexpected node: '" + reader.Name + - "' of type " + reader.NodeType); + throw new XmlException($"Unexpected node: '{reader.Name}' of type {reader.NodeType}"); if (!reader.IsStartElement()) throw new XmlException("Not a start element: " + reader.Name); // We can't use the ReadOuterXml method of reader directly as it places // the cursor on the next element which may result in a blocking read // on the underlying network stream. - using (XmlReader inner = reader.ReadSubtree()) + using XmlReader inner = reader.ReadSubtree(); + inner.Read(); + string xml = inner.ReadOuterXml(); + XmlDocument doc = new(); + using (var sr = new StringReader(xml)) + using (var xtr = new XmlTextReader(sr)) + doc.Load(xtr); + XmlElement elem = (XmlElement)doc.FirstChild; + // Handle unrecoverable stream errors. + if (elem.Name == "stream:error") { - inner.Read(); - string xml = inner.ReadOuterXml(); - XmlDocument doc = new XmlDocument(); - using (var sr = new StringReader(xml)) - using (var xtr = new XmlTextReader(sr)) - doc.Load(xtr); - XmlElement elem = (XmlElement)doc.FirstChild; - // Handle unrecoverable stream errors. - if (elem.Name == "stream:error") - { - string condition = elem.FirstChild != null ? - elem.FirstChild.Name : "undefined"; - //throw new IOException("Unrecoverable stream error: " + condition); - //This indicates a disconnection event - throw new XmppDisconnectionException("Unrecoverable stream error: " + condition); - } - if (expected.Length > 0 && !expected.Contains(elem.Name)) - throw new XmlException("Unexpected XML element: " + elem.Name); - return elem; + string condition = elem.FirstChild != null ? + elem.FirstChild.Name : "undefined"; + //throw new IOException("Unrecoverable stream error: " + condition); + //This indicates a disconnection event + throw new XmppDisconnectionException("Unrecoverable stream error: " + condition); } + return expected.Length > 0 && !expected.Contains(elem.Name) ? throw new XmlException("Unexpected XML element: " + elem.Name) : elem; } /// @@ -159,7 +149,7 @@ private void ReadRootElement() { // Remember the default language communicated by the server. string lang = reader.GetAttribute("xml:lang"); - if (!String.IsNullOrEmpty(lang)) + if (lang?.Length > 0) Language = new CultureInfo(lang); return; } diff --git a/Core/XmppCore.cs b/Core/XmppCore.cs index 2968499a..4ac9a854 100644 --- a/Core/XmppCore.cs +++ b/Core/XmppCore.cs @@ -1,6 +1,4 @@ -using ARSoft.Tools.Net.Dns; -using Net.Xmpp.Core.Sasl; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; @@ -14,8 +12,13 @@ using System.Threading.Tasks; using System.Xml; +using ARSoft.Tools.Net.Dns; + namespace Net.Xmpp.Core { + + using Sasl; + /// /// Implements the core features of the XMPP protocol. /// @@ -25,12 +28,12 @@ public class XmppCore : IDisposable /// /// The DNS SRV name records /// - private List dnsRecordList; + private List? dnsRecordList; /// /// The current SRV DNS record to use /// - private SrvRecord dnsCurrent; + private SrvRecord? dnsCurrent; /// /// Bool variable indicating whether DNS records are initialised @@ -50,7 +53,7 @@ public class XmppCore : IDisposable /// /// The parser instance used for parsing incoming XMPP XML-stream data. /// - private StreamParser parser; + private StreamParser? parser; /// /// True if the instance has been disposed of. @@ -70,74 +73,60 @@ public class XmppCore : IDisposable /// /// The hostname of the XMPP server to connect to. /// - private string hostname; + private string hostname = string.Empty; /// /// The username with which to authenticate. /// - private string username; + private string username = string.Empty; /// /// The password with which to authenticate. /// - private string password; + private string password = string.Empty; /// /// The resource to use for binding. /// - private string resource; + private string? resource; /// /// Write lock for the network stream. /// - private readonly object writeLock = new object(); - - /// - /// The default Time Out for IQ Requests - /// - private int millisecondsDefaultTimeout = -1; - - /// - /// The default value for debugging stanzas is false - /// - private bool debugStanzas = true; + private readonly object writeLock = new(); /// /// A thread-safe dictionary of wait handles for pending IQ requests. /// - private ConcurrentDictionary waitHandles = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary waitHandles = new(); /// /// A thread-safe dictionary of IQ responses for pending IQ requests. /// - private ConcurrentDictionary iqResponses = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary iqResponses = new(); /// /// A thread-safe dictionary of callback methods for asynchronous IQ requests. /// - private ConcurrentDictionary> iqCallbacks = - new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> iqCallbacks = new(); /// /// A cancellation token source that is set when the listener threads shuts /// down due to an exception. /// - private CancellationTokenSource cancelIq = new CancellationTokenSource(); + private CancellationTokenSource cancelIq = new(); /// /// A FIFO of stanzas waiting to be processed. /// - private BlockingCollection stanzaQueue = new BlockingCollection(); + private readonly BlockingCollection stanzaQueue = new(); /// /// A cancellation token source for cancelling the dispatcher, if neccessary. /// - private CancellationTokenSource cancelDispatch = new CancellationTokenSource(); - + private CancellationTokenSource cancelDispatch = new(); - internal event EventHandler OnConnect; + internal event EventHandler? OnConnect; /// /// The hostname of the XMPP server to connect to. @@ -148,10 +137,7 @@ public class XmppCore : IDisposable /// and the value is the empty string. public string Hostname { - get - { - return hostname; - } + get => hostname; set { @@ -163,12 +149,7 @@ public string Hostname /// /// The server adress of the XMPP server to connect to. /// - public string ServerAdress - { - get; - - set; - } + public string ServerAdress { get; set; } /// /// The port number of the XMPP service of the server. @@ -177,10 +158,7 @@ public string ServerAdress /// set and the value is not between 0 and 65536. public int Port { - get - { - return port; - } + get => port; set { @@ -199,10 +177,7 @@ public int Port /// and the value is the empty string. public string Username { - get - { - return username; - } + get => username; set { @@ -218,14 +193,11 @@ public string Username /// set and the value is null. public string Password { - get - { - return password; - } + get => password; set { - value.ThrowIfNull("Password"); + value.ThrowIfNull(nameof(Password)); password = value; } } @@ -233,112 +205,76 @@ public string Password /// /// The Default IQ Set /Request message timeout /// - public int MillisecondsDefaultTimeout - { - get { return millisecondsDefaultTimeout; } - set { millisecondsDefaultTimeout = value; } - } + public int MillisecondsDefaultTimeout { get; set; } = -1; + + /// + /// The Default retry limit for IQ requests + /// + public int DefaultRetryLimit { get; set; } = -1; /// /// Print XML stanzas for debugging purposes /// - public bool DebugStanzas - { - get { return debugStanzas; } - set { debugStanzas = value; } - } + public bool DebugStanzas { get; set; } +#if DEBUG + = true; +#endif /// /// If true the session will be TLS/SSL-encrypted if the server supports it. /// - public bool Tls - { - get; - set; - } + public bool Tls { get; set; } /// /// A delegate used for verifying the remote Secure Sockets Layer (SSL) /// certificate which is used for authentication. /// - public RemoteCertificateValidationCallback Validate - { - get; - set; - } + public RemoteCertificateValidationCallback? Validate { get; set; } /// /// Determines whether the session with the server is TLS/SSL encrypted. /// - public bool IsEncrypted - { - get; - private set; - } + public bool IsEncrypted { get; private set; } /// /// The address of the Xmpp entity. /// - public Jid Jid - { - get; - private set; - } + public Jid Jid { get; private set; } /// /// The default language of the XML stream. /// - public CultureInfo Language - { - get; - private set; - } + public CultureInfo? Language { get; private set; } - private bool connected; /// /// Determines whether the instance is connected to the XMPP server. /// - public bool Connected - { - get - { - return connected; - } - - private set - { - this.connected = value; - } - } + public bool Connected { get; private set; } /// /// Determines whether the instance has been authenticated. /// - public bool Authenticated - { - get; - private set; - } + public bool Authenticated { get; private set; } /// /// The event that is raised when an unrecoverable error condition occurs. /// - public event EventHandler Error; + public event EventHandler? Error; /// /// The event that is raised when an IQ-request stanza has been received. /// - public event EventHandler Iq; + public event EventHandler? Iq; /// /// The event that is raised when a Message stanza has been received. /// - public event EventHandler Message; + public event EventHandler? Message; /// /// The event that is raised when a Presence stanza has been received. /// - public event EventHandler Presence; + public event EventHandler? Presence; /// /// Initializes a new instance of the XmppCore class. @@ -354,6 +290,8 @@ public bool Authenticated /// Layer (SSL) certificate which is used for authentication. Can be null if not /// needed. /// Adress if hostname is diferrent from resolution name + /// The resource identifier to bind with. If this is null, + /// it is assigned by the server. /// The hostname parameter or the /// username parameter or the password parameter is null. /// The hostname parameter or the username @@ -361,14 +299,13 @@ public bool Authenticated /// The value of the port parameter /// is not a valid port number. public XmppCore(string hostname, string username, string password, - int port = 5222, bool tls = true, RemoteCertificateValidationCallback validate = null, - string serverAdress = "") + int port = 5222, bool tls = true, RemoteCertificateValidationCallback? validate = null, + string serverAdress = "", string? resource = null, int defaultTimeoutMs = -1, int defaultMaxRetries = -1, Action? setupEventHandlers = null) { - if (serverAdress == "") - { + if (serverAdress.Length == 0) serverAdress = hostname; - } - moveNextSrvDNS(serverAdress); + + MoveNextSrvDNS(serverAdress); if (dnsCurrent != null) { @@ -386,50 +323,11 @@ public XmppCore(string hostname, string username, string password, Password = password; Tls = tls; Validate = validate; - } - - /// - /// Initializes a new instance of the XmppCore class. - /// - /// The hostname of the XMPP server to connect to. - /// The port number of the XMPP service of the server. - /// If true the session will be TLS/SSL-encrypted if the server - /// supports TLS/SSL-encryption. - /// A delegate used for verifying the remote Secure Sockets - /// Layer (SSL) certificate which is used for authentication. Can be null if not - /// needed. - /// Adress if hostname is diferrent from resolution name - /// The hostname parameter is - /// null. - /// The hostname parameter is the empty - /// string. - /// The value of the port parameter - /// is not a valid port number. - public XmppCore(string hostname, int port = 5222, bool tls = true, - RemoteCertificateValidationCallback validate = null, - string serverAdress = "") - { - if (serverAdress == "") - { - serverAdress = hostname; - } - - moveNextSrvDNS(serverAdress); - - if (dnsCurrent != null) - { - ServerAdress = dnsCurrent.Target.ToString(); - Hostname = hostname; - Port = dnsCurrent.Port; - } - else - { - ServerAdress = serverAdress; - Hostname = hostname; - Port = port; - } - Tls = tls; - Validate = validate; + this.resource = resource; + MillisecondsDefaultTimeout = defaultTimeoutMs; + DefaultRetryLimit = defaultMaxRetries; + setupEventHandlers?.Invoke(this); + Jid = Connect(out client, out stream); } /// @@ -438,7 +336,7 @@ public XmppCore(string hostname, int port = 5222, bool tls = true, /// /// XMPP Domain /// XMPP server hostname for the Domain - private SrvRecord moveNextSrvDNS(string domain) + private SrvRecord? MoveNextSrvDNS(string domain) { domain.ThrowIfNullOrEmpty("domain"); //If already a lookup has being made return @@ -448,10 +346,10 @@ private SrvRecord moveNextSrvDNS(string domain) if (dnsRecordList != null && dnsCurrent != null) dnsRecordList.Remove(dnsCurrent); dnsCurrent = dnsRecordList.FirstOrDefault(); return dnsCurrent; - }; + } dnsIsInit = true; - var domainName = ARSoft.Tools.Net.DomainName.Parse(("_xmpp-client._tcp." + domain)); + var domainName = ARSoft.Tools.Net.DomainName.Parse("_xmpp-client._tcp." + domain); DnsMessage dnsMessage = DnsClient.Default.Resolve(domainName, RecordType.Srv); if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain))) { @@ -467,8 +365,7 @@ private SrvRecord moveNextSrvDNS(string domain) foreach (DnsRecordBase dnsRecord in dnsMessage.AnswerRecords) { - SrvRecord srvRecord = dnsRecord as SrvRecord; - if (srvRecord != null) + if (dnsRecord is SrvRecord srvRecord) { tempList.Add(srvRecord); Console.WriteLine(srvRecord.ToString()); @@ -505,21 +402,25 @@ private SrvRecord moveNextSrvDNS(string domain) /// disposed. /// If a username has been supplied, this method automatically performs /// authentication. - public void Connect(string resource = null) + public void Connect(string? resource) { - if (disposed) - throw new ObjectDisposedException(GetType().FullName); this.resource = resource; + Jid = Connect(out client, out stream); + } + + private Jid Connect(out TcpClient client, out Stream stream) + { + Jid jid; try { - client = new TcpClient(ServerAdress, Port); - client.NoDelay = true; + client = new TcpClient(ServerAdress, Port) { NoDelay = true }; stream = client.GetStream(); // Sets up the connection which includes TLS and possibly SASL negotiation. - SetupConnection(this.resource); + + jid = SetupConnection(); // We are connected. Connected = true; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Connected)); + OnConnect?.Invoke(this, new(ConnectionState.Connected)); // Set up the listener and dispatcher tasks. Task.Factory.StartNew(ReadXmlStream, TaskCreationOptions.LongRunning); Task.Factory.StartNew(DispatchEvents, TaskCreationOptions.LongRunning); @@ -528,6 +429,7 @@ public void Connect(string resource = null) { throw new XmppException("The XML stream could not be negotiated.", e); } + return jid; } /// @@ -555,8 +457,8 @@ public void Connect(string resource = null) public void Authenticate(string username, string password) { AssertValid(); - username.ThrowIfNull("username"); - password.ThrowIfNull("password"); + username.ThrowIfNull(nameof(username)); + password.ThrowIfNull(nameof(password)); if (Authenticated) throw new XmppException("Authentication has already been performed."); // Unfortunately, SASL authentication does not follow the standard XMPP @@ -565,16 +467,16 @@ public void Authenticate(string username, string password) Username = username; Password = password; Disconnect(); - Connect(this.resource); + Jid = Connect(out client, out stream); } public void Reconnect() { AssertValid(); - Username.ThrowIfNull("username"); - Password.ThrowIfNull("password"); + Username.ThrowIfNull(nameof(username)); + Password.ThrowIfNull(nameof(password)); Disconnect(); - Connect(this.resource); + Jid = Connect(out client, out stream); } /// @@ -593,8 +495,8 @@ public void Reconnect() /// connected to a remote host. /// There was a failure while writing to the /// network. - public void SendMessage(Jid to = null, Jid from = null, XmlElement data = null, - string id = null, CultureInfo language = null) + public void SendMessage(Jid? to = null, Jid? from = null, XmlElement? data = null, + string? id = null, CultureInfo? language = null) { AssertValid(); Send(new Message(to, from, data, id, language)); @@ -615,7 +517,7 @@ public void SendMessage(Jid to = null, Jid from = null, XmlElement data = null, public void SendMessage(Message message) { AssertValid(); - message.ThrowIfNull("message"); + message.ThrowIfNull(nameof(message)); Send(message); } @@ -625,21 +527,19 @@ public void SendMessage(Message message) /// /// The JID of the intended recipient for the stanza. /// The JID of the sender. - /// he content of the stanza. /// The ID of the stanza. - /// The language of the XML character data of - /// the stanza. + /// The language of the XML character data of the stanza. + /// he content of the stanza. /// The XmppCore object has been /// disposed. /// The XmppCore instance is not /// connected to a remote host. /// There was a failure while writing to the /// network. - public void SendPresence(Jid to = null, Jid from = null, string id = null, - CultureInfo language = null, params XmlElement[] data) + public void SendPresence(Jid? to = null, Jid? from = null, string? id = null, + CultureInfo? language = null, params XmlElement[] data) { - AssertValid(); - Send(new Presence(to, from, id, language, data)); + SendPresence(new(to, from, id, language, data)); } /// @@ -657,7 +557,7 @@ public void SendPresence(Jid to = null, Jid from = null, string id = null, public void SendPresence(Presence presence) { AssertValid(); - presence.ThrowIfNull("presence"); + presence.ThrowIfNull(nameof(presence)); Send(presence); } @@ -687,12 +587,12 @@ public void SendPresence(Presence presence) /// network, or there was a failure reading from the network. /// A timeout was specified and it /// expired. - public Iq IqRequest(IqType type, Jid to = null, Jid from = null, - XmlElement data = null, CultureInfo language = null, - int millisecondsTimeout = -1) + public Iq IqRequest(IqType type, Jid? to = null, Jid? from = null, + XmlElement? data = null, CultureInfo? language = null, + int millisecondsTimeout = -1, int maxRetries = -1) { AssertValid(); - return IqRequest(new Iq(type, null, to, from, data, language), millisecondsTimeout); + return IqRequest(new Iq(type, null, to, from, data, language), millisecondsTimeout, maxRetries); } /// @@ -716,61 +616,62 @@ public Iq IqRequest(IqType type, Jid to = null, Jid from = null, /// network, or there was a failure reading from the network. /// A timeout was specified and it /// expired. - public Iq IqRequest(Iq request, int millisecondsTimeout = -1) + public Iq IqRequest(Iq request, int millisecondsTimeout = -1, int maxRetries = -1) { - int timeOut = -1; AssertValid(); - request.ThrowIfNull("request"); - if (request.Type != IqType.Set && request.Type != IqType.Get) + request.ThrowIfNull(nameof(request)); + if (request.Type is not IqType.Set and not IqType.Get) throw new ArgumentException("The IQ type must be either 'set' or 'get'."); - if (millisecondsTimeout == -1) - { - timeOut = millisecondsDefaultTimeout; - } - else timeOut = millisecondsTimeout; + int timeOut = millisecondsTimeout == -1 ? MillisecondsDefaultTimeout : millisecondsTimeout; + int remainingRetries = maxRetries == -1 ? DefaultRetryLimit : maxRetries; // Generate a unique ID for the IQ request. request.Id = GetId(); - AutoResetEvent ev = new AutoResetEvent(false); - Send(request); - // Wait for event to be signaled by task that processes the incoming - // XML stream. - waitHandles[request.Id] = ev; - int index = WaitHandle.WaitAny(new WaitHandle[] { ev, cancelIq.Token.WaitHandle }, - timeOut); - if (index == WaitHandle.WaitTimeout) + AutoResetEvent ev = new(false); + while (true) { - //An entity that receives an IQ request of type "get" or "set" MUST reply with an IQ response of type - //"result" or "error" (the response MUST preserve the 'id' attribute of the request). - //http://xmpp.org/rfcs/rfc3920.html#stanzas - //if (request.Type == IqType.Set || request.Type == IqType.Get) + Send(request); + // Wait for event to be signaled by task that processes the incoming + // XML stream. + waitHandles[request.Id] = ev; + int index = WaitHandle.WaitAny(new[] { ev, cancelIq.Token.WaitHandle }, timeOut); + if (index == WaitHandle.WaitTimeout) + { + //An entity that receives an IQ request of type "get" or "set" MUST reply with an IQ response of type + //"result" or "error" (the response MUST preserve the 'id' attribute of the request). + //http://xmpp.org/rfcs/rfc3920.html#stanzas + //if (request.Type == IqType.Set || request.Type == IqType.Get) - //Make sure that its a request towards the server and not towards any client - var ping = request.Data["ping"]; + //Make sure that its a request towards the server and not towards any client + var ping = request.Data["ping"]; - if (request.To.Domain == Jid.Domain && (request.To.Node == null || request.To.Node == "") && (ping != null && ping.NamespaceURI == "urn:xmpp:ping")) - { - if (Connected) + if (request.To is not null && request.To.Domain == Jid?.Domain && request.To.Node?.Length > 0 && (ping?.NamespaceURI == "urn:xmpp:ping")) { - Connected = false; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Lost)); + if (Connected) + { + Connected = false; + OnConnect?.Invoke(this, new(ConnectionState.Lost)); + } + if (!disposed) + Error?.Invoke(this, new(new XmppDisconnectionException("Timeout Disconnection happened at IqRequest"))); } - var e = new XmppDisconnectionException("Timeout Disconnection happened at IqRequest"); - if (!disposed) - Error.Raise(this, new ErrorEventArgs(e)); - //throw new TimeoutException(); + + if (remainingRetries-- != -1) + continue; + + throw new TimeoutException(); } + // Reader task errored out. + if (index == 1) + throw new IOException("The incoming XML stream could not read."); - //This check is somehow not really needed doue to the IQ must be either set or get + break; } - // Reader task errored out. - if (index == 1) - throw new IOException("The incoming XML stream could not read."); + // Fetch response stanza. - Iq response; - if (iqResponses.TryRemove(request.Id, out response)) + if (iqResponses.TryRemove(request.Id, out Iq response)) return response; - // Shouldn't happen. + // Shouldn't happen. throw new InvalidOperationException(); } @@ -796,12 +697,12 @@ public Iq IqRequest(Iq request, int millisecondsTimeout = -1) /// connected to a remote host. /// There was a failure while writing to the /// network. - public string IqRequestAsync(IqType type, Jid to = null, Jid from = null, - XmlElement data = null, CultureInfo language = null, - Action callback = null) + public string IqRequest(IqType type, Jid? to = null, Jid? from = null, + XmlElement? data = null, CultureInfo? language = null, + Action? callback = null) { AssertValid(); - return IqRequestAsync(new Iq(type, null, to, from, data, language), callback); + return IqRequest(new Iq(type, null, to, from, data, language), callback); } /// @@ -821,11 +722,11 @@ public string IqRequestAsync(IqType type, Jid to = null, Jid from = null, /// connected to a remote host. /// There was a failure while writing to the /// network. - public string IqRequestAsync(Iq request, Action callback = null) + public string IqRequest(Iq request, Action? callback = null) { AssertValid(); - request.ThrowIfNull("request"); - if (request.Type != IqType.Set && request.Type != IqType.Get) + request.ThrowIfNull(nameof(request)); + if (request.Type is not IqType.Set and not IqType.Get) throw new ArgumentException("The IQ type must be either 'set' or 'get'."); request.Id = GetId(); // Register the callback. @@ -854,8 +755,8 @@ public string IqRequestAsync(Iq request, Action callback = null) /// connected to a remote host. /// There was a failure while writing to the /// network. - public void IqResponse(IqType type, string id, Jid to = null, Jid from = null, - XmlElement data = null, CultureInfo language = null) + public void IqResponse(IqType type, string id, Jid? to = null, Jid? from = null, + XmlElement? data = null, CultureInfo? language = null) { AssertValid(); IqResponse(new Iq(type, id, to, from, data, null)); @@ -878,8 +779,8 @@ public void IqResponse(IqType type, string id, Jid to = null, Jid from = null, public void IqResponse(Iq response) { AssertValid(); - response.ThrowIfNull("response"); - if (response.Type != IqType.Result && response.Type != IqType.Error) + response.ThrowIfNull(nameof(response)); + if (response.Type is not IqType.Result and not IqType.Error) throw new ArgumentException("The IQ type must be either 'result' or 'error'."); Send(response); } @@ -925,12 +826,10 @@ protected virtual void Dispose(bool disposing) // Get rid of managed resources. if (disposing) { - if (parser != null) - parser.Close(); - parser = null; - if (client != null) - client.Close(); - client = null; + parser?.Close(); + parser = null!; + client?.Close(); + client = null!; } // Get rid of unmanaged resources. } @@ -957,8 +856,6 @@ private void AssertValid() /// /// Negotiates an XML stream over which XML stanzas can be sent. /// - /// The resource identifier to bind with. If this is null, - /// it is assigned by the server. /// The resource binding process failed. /// Invalid or unexpected XML data has been /// received from the XMPP server. @@ -966,25 +863,21 @@ private void AssertValid() /// trying to establish a secure connection, or the provided credentials were /// rejected by the server, or the server requires TLS/SSL and TLS has been /// turned off. - private void SetupConnection(string resource = null) + private Jid SetupConnection() { // Request the initial stream. - XmlElement feats = InitiateStream(Hostname); + var feats = InitiateStream(Hostname); // Server supports TLS/SSL via STARTTLS. if (feats["starttls"] != null) { // TLS is mandatory and user opted out of it. - if (feats["starttls"]["required"] != null && Tls == false) + if (feats["starttls"]["required"] != null && !Tls) throw new AuthenticationException("The server requires TLS/SSL."); - if (Tls) + if (Tls && Validate is not null) feats = StartTls(Hostname, Validate); } - // If no Username has been provided, don't perform authentication. - if (Username == null) - return; - // Construct a list of SASL mechanisms supported by the server. var m = feats["mechanisms"]; - if (m == null || !m.HasChildNodes) + if (m?.HasChildNodes != true) throw new AuthenticationException("No SASL mechanisms advertised."); var mech = m.FirstChild; var list = new HashSet(); @@ -993,19 +886,18 @@ private void SetupConnection(string resource = null) list.Add(mech.InnerText); mech = mech.NextSibling; } + Jid jid; // Continue with SASL authentication. try { feats = Authenticate(list, Username, Password, Hostname); - // FIXME: How is the client's JID constructed if the server does not support - // resource binding? - if (feats["bind"] != null) - Jid = BindResource(resource); + jid = BindResource(resource); } catch (SaslException e) { throw new AuthenticationException("Authentication failed.", e); } + return jid; } /// @@ -1030,8 +922,7 @@ private XmlElement InitiateStream(string hostname) .Attr("xml:lang", CultureInfo.CurrentCulture.Name); Send(xml.ToXmlString(xmlDeclaration: true, leaveOpen: true)); // Create a new parser instance. - if (parser != null) - parser.Close(); + parser?.Close(); parser = new StreamParser(stream, true); // Remember the default language of the stream. The server is required to // include this, but we make sure nonetheless. @@ -1065,7 +956,7 @@ private XmlElement StartTls(string hostname, SendAndReceive(Xml.Element("starttls", "urn:ietf:params:xml:ns:xmpp-tls"), "proceed"); // Complete TLS negotiation and switch to secure stream. - SslStream sslStream = new SslStream(stream, false, validate ?? + SslStream sslStream = new(stream, false, validate ?? ((sender, cert, chain, err) => true)); sslStream.AuthenticateAsClient(hostname); stream = sslStream; @@ -1097,14 +988,16 @@ private XmlElement Authenticate(IEnumerable mechanisms, string username, { string name = SelectMechanism(mechanisms); SaslMechanism m = SaslFactory.Create(name); - m.Properties.Add("Username", username); - m.Properties.Add("Password", password); + m.Properties.Add(nameof(Username), username); + m.Properties.Add(nameof(Password), password); var xml = Xml.Element("auth", "urn:ietf:params:xml:ns:xmpp-sasl") .Attr("mechanism", name) - .Text(m.HasInitial ? m.GetResponse(String.Empty) : String.Empty); + .Text(m.HasInitial ? m.GetResponse(string.Empty) : string.Empty); Send(xml); while (true) { + if (parser is null) + throw new ObjectDisposedException(nameof(XmppCore)); XmlElement ret = parser.NextElement("challenge", "success", "failure"); if (ret.Name == "failure") throw new SaslException("SASL authentication failed."); @@ -1117,7 +1010,7 @@ private XmlElement Authenticate(IEnumerable mechanisms, string username, // verified. if (ret.Name == "success") { - if (response == String.Empty) + if (response?.Length == 0) break; throw new SaslException("Could not verify server's signature."); } @@ -1166,7 +1059,7 @@ private string SelectMechanism(IEnumerable mechanisms) /// or unexpected XML data. /// There was a failure while writing to the /// network, or there was a failure while reading from the network. - private Jid BindResource(string resourceName = null) + private Jid BindResource(string? resourceName = null) { var xml = Xml.Element("iq") .Attr("type", "set") @@ -1176,9 +1069,9 @@ private Jid BindResource(string resourceName = null) bind.Child(Xml.Element("resource").Text(resourceName)); xml.Child(bind); XmlElement res = SendAndReceive(xml, "iq"); - if (res["bind"] == null || res["bind"]["jid"] == null) - throw new XmppException("Erroneous server response."); - return new Jid(res["bind"]["jid"].InnerText); + return res["bind"]?["jid"] is { } node + ? new Jid(node.InnerText) + : throw new XmppException("Erroneous server response."); } /// @@ -1191,7 +1084,7 @@ private Jid BindResource(string resourceName = null) /// to the network. private void Send(XmlElement element) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); Send(element.ToXmlString()); } @@ -1204,7 +1097,10 @@ private void Send(XmlElement element) /// the network. private void Send(string xml) { - xml.ThrowIfNull("xml"); + xml.ThrowIfNull(nameof(xml)); + if (stream is null) + throw new ObjectDisposedException(nameof(XmppCore)); + // XMPP is guaranteed to be UTF-8. byte[] buf = Encoding.UTF8.GetBytes(xml); lock (writeLock) @@ -1215,14 +1111,14 @@ private void Send(string xml) { stream.Write(buf, 0, buf.Length); stream.Flush(); - if (debugStanzas) System.Diagnostics.Debug.WriteLine(xml); + if (DebugStanzas) System.Diagnostics.Debug.WriteLine(xml); } catch (IOException e) { if (Connected) { Connected = false; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Lost)); + OnConnect?.Invoke(this, new(ConnectionState.Lost)); } throw new XmppDisconnectionException(e.Message, e); } @@ -1239,7 +1135,7 @@ private void Send(string xml) /// the network. private void Send(Stanza stanza) { - stanza.ThrowIfNull("stanza"); + stanza.ThrowIfNull(nameof(stanza)); Send(stanza.ToString()); } @@ -1261,18 +1157,20 @@ private XmlElement SendAndReceive(XmlElement element, params string[] expected) { Send(element); + if (parser is null) + throw new ObjectDisposedException(nameof(XmppCore)); try { return parser.NextElement(expected); } - catch (XmppDisconnectionException e) + catch (XmppDisconnectionException) { if (Connected) { Connected = false; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Lost)); + OnConnect?.Invoke(this, new(ConnectionState.Lost)); } - throw e; + throw; } } @@ -1287,12 +1185,15 @@ private void ReadXmlStream() { while (true) { + if (parser is null) + throw new ObjectDisposedException(nameof(XmppCore)); + XmlElement elem = parser.NextElement("iq", "message", "presence"); // Parse element and dispatch. switch (elem.Name) { case "iq": - Iq iq = new Iq(elem); + Iq iq = new(elem); if (iq.IsResponse) { if (!HandleIqResponseBlocking(iq)) @@ -1313,8 +1214,6 @@ private void ReadXmlStream() case "presence": stanzaQueue.Add(new Presence(elem)); break; - default: - break; } } } @@ -1327,19 +1226,18 @@ private void ReadXmlStream() cancelIq.Cancel(); cancelIq = new CancellationTokenSource(); //Add the failed connection - if ((e is IOException) || (e is XmppDisconnectionException)) + if (e is IOException or XmppDisconnectionException) { if (Connected) { Connected = false; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Lost)); + OnConnect?.Invoke(this, new(ConnectionState.Lost)); } - var ex = new XmppDisconnectionException(e.ToString()); - e = ex; + e = new XmppDisconnectionException(e.ToString()); } // Raise the error event. if (!disposed) - Error.Raise(this, new ErrorEventArgs(e)); + Error?.Invoke(this, new(e)); } } @@ -1356,18 +1254,19 @@ private void DispatchEvents() try { Stanza stanza = stanzaQueue.Take(cancelDispatch.Token); - if (debugStanzas) System.Diagnostics.Debug.WriteLine(stanza.ToString()); - if (stanza is Iq) - { - if (!((stanza as Iq).IsResponse && HandleIqResponseAsync(stanza as Iq))) - Iq.Raise(this, new IqEventArgs(stanza as Iq)); - } - else if (stanza is Message) + if (DebugStanzas) System.Diagnostics.Debug.WriteLine(stanza.ToString()); + switch (stanza) { - Message.Raise(this, new MessageEventArgs(stanza as Message)); + case Iq iq when !(iq.IsResponse && HandleIqResponse(iq)): + Iq?.Invoke(this, new(iq)); + break; + case Message message: + Message?.Invoke(this, new(message)); + break; + case Presence presence: + Presence?.Invoke(this, new(presence)); + break; } - else if (stanza is Presence) - Presence.Raise(this, new PresenceEventArgs(stanza as Presence)); } catch (OperationCanceledException) { @@ -1390,25 +1289,21 @@ private void DispatchEvents() /// The received IQ response stanza. private bool HandleIqResponseBlocking(Iq iq) { - AutoResetEvent ev; - string id = iq.Id; // Signal the event if it's a blocking call. - if (waitHandles.TryRemove(id, out ev)) + if (iq.Id?.Length > 0 && waitHandles.TryRemove(iq.Id, out var ev)) { - iqResponses[id] = iq; + iqResponses[iq.Id] = iq; ev.Set(); return true; } - return false; + return false; } - private bool HandleIqResponseAsync(Iq iq) + private bool HandleIqResponse(Iq iq) { - string id = iq.Id; - Action cb; - if (iqCallbacks.TryRemove(id, out cb)) + if (iq.Id?.Length > 0 && iqCallbacks.TryRemove(iq.Id, out var cb)) { - cb(id, iq); + cb(iq.Id, iq); return true; } @@ -1434,7 +1329,7 @@ private void Disconnect() return; Connected = false; Authenticated = false; - OnConnect?.Raise(this, new ConnectEventArgs(ConnectionState.Disconnected)); + OnConnect?.Invoke(this, new(ConnectionState.Disconnected)); // Close the XML stream. Send(""); } diff --git a/Extensions/CustomExtension/CustomIqExtension.cs b/Extensions/CustomExtension/CustomIqExtension.cs index fae01bc8..af7a3653 100644 --- a/Extensions/CustomExtension/CustomIqExtension.cs +++ b/Extensions/CustomExtension/CustomIqExtension.cs @@ -1,9 +1,10 @@ -using Net.Xmpp.Core; -using Net.Xmpp.Im; -using System; +using System; using System.Collections.Generic; using System.Xml; +using Net.Xmpp.Core; +using Net.Xmpp.Im; + namespace Net.Xmpp.Extensions { /// @@ -11,43 +12,25 @@ namespace Net.Xmpp.Extensions /// internal class CustomIqExtension : XmppExtension, IInputFilter { + private const string customIqNS = "urn:sharp.xmpp:customiq"; + /// /// A reference to the 'Entity Capabilities' extension instance. /// - private EntityCapabilities ecapa; + private readonly EntityCapabilities ecapa; /// /// An enumerable collection of XMPP namespaces the extension implements. /// /// This is used for compiling the list of supported extensions /// advertised by the 'Service Discovery' extension. - public override IEnumerable Namespaces - { - get - { - return new string[] { "urn:sharp.xmpp:customiq" }; - } - } + public override IEnumerable Namespaces => new string[] { customIqNS }; /// /// The named constant of the Extension enumeration that corresponds to this /// extension. /// - public override Extension Xep - { - get - { - return Extension.CustomIqExtension; - } - } - - /// - /// Invoked after all extensions have been loaded. - /// - public override void Initialize() - { - ecapa = im.GetExtension(); - } + public override Extension Xep => Extension.CustomIqExtension; /// /// Invoked when an IQ stanza is being received. @@ -62,29 +45,29 @@ public override void Initialize() /// on to the next handler. public bool Input(Iq stanza) { - string response = null; + string? response = null; //if (stanza.Type != IqType.Get) // return false; //get,set, result are supported var customIqStanza = stanza.Data["customiq"]; - if (customIqStanza == null || customIqStanza.NamespaceURI != "urn:sharp.xmpp:customiq") + if (customIqStanza == null || customIqStanza.NamespaceURI != customIqNS || stanza.From is null) return false; //Result indicates that the request has been received. //It has not to do with the semantics of the message XmlElement query = stanza.Data["customiq"]; - XmlDocument targetDocument = new XmlDocument(); + XmlDocument targetDocument = new(); CopyNodes(targetDocument, targetDocument, query.FirstChild); - var xmlresponse = Xml.Element("customiq", "urn:sharp.xmpp:customiq"); + var xmlresponse = Xml.Element("customiq", customIqNS); try { //call the callback for receiving a relevant stanza //and wait for answer in order provide it - response = im.CustomIqDelegate.Invoke(stanza.From, targetDocument.InnerXml); + response = im.CustomIqDelegate?.Invoke(stanza.From, targetDocument.InnerXml); - if (response != null && response != "") + if (response?.Length > 0) { xmlresponse.Text(response); } @@ -118,7 +101,7 @@ public void CopyNodes(XmlDocument targetDocument, XmlNode targetNode, XmlNode so /// /// Requests the XMPP entity with the specified JID a GET command. - /// When the Result is received and it not not an error + /// When the Result is received and it not an error /// if fires the callback function /// /// The JID of the XMPP entity to get. @@ -133,8 +116,8 @@ public void CopyNodes(XmlDocument targetDocument, XmlNode targetNode, XmlNode so /// unspecified XMPP error occurred. public void RequestCustomIqAsync(Jid jid, string request, Action callback) { - jid.ThrowIfNull("jid"); - request.ThrowIfNull("str"); + jid.ThrowIfNull(nameof(jid)); + request.ThrowIfNull(nameof(request)); //First check if the Jid entity supports the namespace if (!ecapa.Supports(jid, Extension.CustomIqExtension)) @@ -142,10 +125,10 @@ public void RequestCustomIqAsync(Jid jid, string request, Action callback) throw new NotSupportedException("The XMPP entity does not support the " + "'CustomIqExtension' extension."); } - var xml = Xml.Element("customiq", "urn:sharp.xmpp:customiq").Text(request); + var xml = Xml.Element("customiq", customIqNS).Text(request); //The Request is Async - im.IqRequestAsync(IqType.Get, jid, im.Jid, xml, null, (id, iq) => + im.IqRequestCallback(IqType.Get, jid, im.Jid, xml, null, (id, iq) => { //For any reply we execute the callback if (iq.Type == IqType.Error) @@ -155,10 +138,7 @@ public void RequestCustomIqAsync(Jid jid, string request, Action callback) try { //An empty response means the message was received - if (callback != null) - { - callback.Invoke(); - } + callback?.Invoke(); } catch (Exception e) { @@ -171,7 +151,7 @@ public void RequestCustomIqAsync(Jid jid, string request, Action callback) /// /// Requests the XMPP entity with the specified JID a GET command. - /// When the Result is received and it not not an error + /// When the Result is received and it not an error /// if fires the callback function /// /// The JID of the XMPP entity to get. @@ -186,8 +166,8 @@ public void RequestCustomIqAsync(Jid jid, string request, Action callback) /// unspecified XMPP error occurred. public void RequestCustomIq(Jid jid, string request) { - jid.ThrowIfNull("jid"); - request.ThrowIfNull("str"); + jid.ThrowIfNull(nameof(jid)); + request.ThrowIfNull(nameof(request)); //First check if the Jid entity supports the namespace if (!ecapa.Supports(jid, Extension.CustomIqExtension)) @@ -195,7 +175,7 @@ public void RequestCustomIq(Jid jid, string request) throw new NotSupportedException("The XMPP entity does not support the " + "'CustomIqExtension' extension."); } - var xml = Xml.Element("customiq", "urn:sharp.xmpp:customiq").Text(request); + var xml = Xml.Element("customiq", customIqNS).Text(request); //The Request is Async im.IqRequest(IqType.Get, jid, im.Jid, xml); @@ -206,9 +186,10 @@ public void RequestCustomIq(Jid jid, string request) /// /// A reference to the XmppIm instance on whose behalf this /// instance is created. - public CustomIqExtension(XmppIm im) + public CustomIqExtension(XmppIm im, EntityCapabilities ecapa) : base(im) { + this.ecapa = ecapa; } } } \ No newline at end of file diff --git a/Extensions/CustomExtension/CustomIqRequestDelegate.cs b/Extensions/CustomExtension/CustomIqRequestDelegate.cs index cb4acf40..1a120586 100644 --- a/Extensions/CustomExtension/CustomIqRequestDelegate.cs +++ b/Extensions/CustomExtension/CustomIqRequestDelegate.cs @@ -3,8 +3,8 @@ /// /// Invoked when a CustomIqRequest is made. /// - /// The jid + /// The jid of the sender /// The serialised data stream /// The serialised anwser string - public delegate string CustomIqRequestDelegate(Jid jid, string str); + public delegate string CustomIqRequestDelegate(Jid from, string str); } \ No newline at end of file diff --git a/Extensions/Extension.cs b/Extensions/Extension.cs index 6dfe7cd5..6a176893 100644 --- a/Extensions/Extension.cs +++ b/Extensions/Extension.cs @@ -63,13 +63,13 @@ public enum Extension UserAvatar, /// - /// An extension for for communicating information about user moods, such + /// An extension for communicating information about user moods, such /// as whether a person is currently happy, sad, angy, or annoyed. /// UserMood, /// - /// An extension extension for data forms that can be used in workflows + /// An extension for data forms that can be used in workflows /// such as service configuration as well as for application-specific /// data description and reporting. /// @@ -175,7 +175,17 @@ public enum Extension /// (many-to-many chat) /// MultiUserChat, - + + /// + /// Service-level tasks that administrators often need to perform in relation to Jabber/XMPP servers and components + /// + ServiceAdministration, + + /// + /// An extension that enables an entity to initiate a command session where there is no preferred namespace + /// + AdHocCommands, + /// /// An extension for JabberSearch /// diff --git a/Extensions/XEP-0004/DataForms.cs b/Extensions/XEP-0004/DataForms.cs index a640d7a4..f540119a 100644 --- a/Extensions/XEP-0004/DataForms.cs +++ b/Extensions/XEP-0004/DataForms.cs @@ -13,25 +13,13 @@ internal class DataForms : XmppExtension /// /// This is used for compiling the list of supported extensions /// advertised by the 'Service Discovery' extension. - public override IEnumerable Namespaces - { - get - { - return new string[] { "jabber:x:data" }; - } - } + public override IEnumerable Namespaces => new string[] { "jabber:x:data" }; /// /// The named constant of the Extension enumeration that corresponds to this /// extension. /// - public override Extension Xep - { - get - { - return Extension.DataForms; - } - } + public override Extension Xep => Extension.DataForms; /// /// Initializes a new instance of the DataForms class. diff --git a/Extensions/XEP-0004/Dataforms/BooleanField.cs b/Extensions/XEP-0004/Dataforms/BooleanField.cs index fa83909b..ab87574a 100644 --- a/Extensions/XEP-0004/Dataforms/BooleanField.cs +++ b/Extensions/XEP-0004/Dataforms/BooleanField.cs @@ -16,19 +16,17 @@ public bool? Value get { var v = element["value"]; - if (v == null) - return null; - return ParseValue(v.InnerText); + return v == null ? null : ParseValue(v.InnerText); } private set { - if (element["value"] != null) + if (element["value"] is { } node) { - if (value == null) - element.RemoveChild(element["value"]); + if (value is null) + element.RemoveChild(node); else - element["value"].InnerText = value.ToString().ToLower(); + node.InnerText = value.ToString().ToLower(); } else { @@ -51,11 +49,11 @@ private set /// The default value of the field. /// The name parameter is /// null. - public BooleanField(string name, bool required = false, string label = null, - string description = null, bool? value = null) + public BooleanField(string name, bool required = false, string? label = null, + string? description = null, bool? value = null) : base(DataFieldType.Boolean, name, required, label, description) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); Value = value; } @@ -99,8 +97,8 @@ internal BooleanField(XmlElement element) /// null. private bool ParseValue(string value) { - value.ThrowIfNull("value"); - if (value == "0" || value == "false") + value.ThrowIfNull(nameof(value)); + if (value is "0" or "false") return false; // Be lenient. return true; diff --git a/Extensions/XEP-0004/Dataforms/DataField.cs b/Extensions/XEP-0004/Dataforms/DataField.cs index dbdf474e..543f82c0 100644 --- a/Extensions/XEP-0004/Dataforms/DataField.cs +++ b/Extensions/XEP-0004/Dataforms/DataField.cs @@ -24,17 +24,17 @@ public class DataField /// /// A human-readable name for the field. /// - public string Label + public string? Label { get { var v = element.GetAttribute("label"); - return String.IsNullOrEmpty(v) ? null : v; + return v?.Length > 0 ? v : null; } private set { - if (value == null) + if (value is null) element.RemoveAttribute("label"); else element.SetAttribute("label", value); @@ -45,21 +45,16 @@ private set /// A natural-language description of the field, intended for presentation /// in a user-agent. /// - public string Description + public string? Description { - get - { - if (element["desc"] != null) - return element["desc"].InnerText; - return null; - } + get => element["desc"]?.InnerText; private set { var e = element["desc"]; if (e != null) { - if (value == null) + if (value is null) element.RemoveChild(e); else e.InnerText = value; @@ -77,14 +72,11 @@ private set /// public bool Required { - get - { - return element["required"] != null; - } + get => element["required"] != null; private set { - if (value == false) + if (!value) { if (element["required"] != null) element.RemoveChild(element["required"]); @@ -105,12 +97,14 @@ public string Name get { var v = element.GetAttribute("var"); - return String.IsNullOrEmpty(v) ? null : v; + return v?.Length > 0 ? v + : Type == DataFieldType.Fixed ? "" + : throw new ArgumentNullException(nameof(Name)); } private set { - if (value == null) + if (value is null) element.RemoveAttribute("var"); else element.SetAttribute("var", value); @@ -124,15 +118,9 @@ private set /// XML element is invalid. public DataFieldType? Type { - get - { - return GetDataFieldType(); - } + get => GetDataFieldType(); - private set - { - SetType(value); - } + private set => SetType(value); } /// @@ -179,12 +167,12 @@ public IEnumerable Values /// A human-readable name for the field. /// A natural-language description of the field, /// intended for presentation in a user-agent. - public DataField(DataFieldType type, string name = null, bool required = false, - string label = null, string description = null) + public DataField(DataFieldType type, string? name = null, bool required = false, + string? label = null, string? description = null) { element = Xml.Element("field", xmlns); Type = type; - Name = name; + Name = name ?? (type == DataFieldType.Fixed ? "" : throw new ArgumentNullException(nameof(Name))); Required = required; Label = label; Description = description; @@ -202,7 +190,7 @@ public DataField(DataFieldType type, string name = null, bool required = false, /// valid data-field element. internal DataField(XmlElement element) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); this.element = element; try { @@ -255,13 +243,13 @@ private void SetType(DataFieldType? type) /// A string representing the specified value. private string TypeToAttributeValue(DataFieldType type) { - StringBuilder b = new StringBuilder(); + StringBuilder b = new(); string s = type.ToString(); for (int i = 0; i < s.Length; i++) { - if (Char.IsUpper(s, i) && i > 0) + if (char.IsUpper(s, i) && i > 0) b.Append('-'); - b.Append(Char.ToLower(s[i])); + b.Append(char.ToLower(s[i])); } return b.ToString(); } @@ -280,13 +268,13 @@ private string TypeToAttributeValue(DataFieldType type) /// the named constants of the DataFieldType enumeration. private DataFieldType AttributeValueToType(string value) { - value.ThrowIfNull("value"); - StringBuilder b = new StringBuilder(); + value.ThrowIfNull(nameof(value)); + StringBuilder b = new(); string s = value; for (int i = 0; i < s.Length; i++) { if (s[i] == '-') - b.Append(Char.ToUpper(s[++i])); + b.Append(char.ToUpper(s[++i])); else b.Append(s[i]); } @@ -306,9 +294,7 @@ private DataFieldType AttributeValueToType(string value) try { string t = element.GetAttribute("type"); - if (String.IsNullOrEmpty(t)) - return null; - return AttributeValueToType(t); + return t?.Length > 0 ? (DataFieldType?)AttributeValueToType(t) : null; } catch (Exception e) { diff --git a/Extensions/XEP-0004/Dataforms/DataForm.cs b/Extensions/XEP-0004/Dataforms/DataForm.cs index 31050e23..34d53615 100644 --- a/Extensions/XEP-0004/Dataforms/DataForm.cs +++ b/Extensions/XEP-0004/Dataforms/DataForm.cs @@ -9,12 +9,7 @@ namespace Net.Xmpp.Extensions.Dataforms /// public abstract class DataForm { - private const string xmlns = "jabber:x:data"; - - /// - /// The fields contained in the data-form. - /// - private FieldList fields; + private const string xmlns = "jabber:x:data"; /// /// The underlying XML element representing the data-form. @@ -24,21 +19,16 @@ public abstract class DataForm /// /// The title of the data-form. /// - public string Title + public string? Title { - get - { - if (element["title"] != null) - return element["title"].InnerText; - return null; - } + get => element["title"]?.InnerText; set { var e = element["title"]; if (e != null) { - if (value == null) + if (value is null) element.RemoveChild(e); else e.InnerText = value; @@ -55,21 +45,16 @@ public string Title /// The natural-language instructions to be followed by the /// form-submitting entity. /// - public string Instructions + public string? Instructions { - get - { - if (element["instructions"] != null) - return element["instructions"].InnerText; - return null; - } + get => element["instructions"]?.InnerText; set { var e = element["instructions"]; if (e != null) { - if (value == null) + if (value is null) element.RemoveChild(e); else e.InnerText = value; @@ -89,27 +74,15 @@ public string Instructions /// XML element is invalid. public DataFormType Type { - get - { - return GetDataFormType(); - } + get => GetDataFormType(); - protected set - { - element.SetAttribute("type", value.ToString().ToLower()); - } + protected set => element.SetAttribute("type", value.ToString().ToLower()); } /// /// A list of fields contained in the data-form. /// - public FieldList Fields - { - get - { - return fields; - } - } + public FieldList Fields { get; } /// /// Returns a textual XML representation of the data-form. @@ -140,18 +113,18 @@ public XmlElement ToXmlElement() /// which no fields may be added, otherwise false. /// One or several data-fields to add to the /// form. - internal DataForm(string title = null, string instructions = null, + internal DataForm(string? title = null, string? instructions = null, bool readOnly = false, params DataField[] fields) { element = Xml.Element("x", xmlns); Title = title; Instructions = instructions; - this.fields = new FieldList(element, readOnly); + this.Fields = new FieldList(element, readOnly); if (fields != null) { foreach (var f in fields) if (f != null) - this.fields.Add(f); + this.Fields.Add(f); } } @@ -169,11 +142,11 @@ internal DataForm(string title = null, string instructions = null, /// valid data-form element. internal DataForm(XmlElement element, bool readOnly = false) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); this.element = element; try { - fields = new FieldList(element, readOnly); + Fields = new FieldList(element, readOnly); // Call GetDataFormType method to verify the 'type' attribute. GetDataFormType(); } @@ -218,8 +191,8 @@ private DataFormType GetDataFormType() "XML element is invalid.", e); } } - - /// + + /// /// Add a boolean value to this form /// /// The name of the value diff --git a/Extensions/XEP-0004/Dataforms/DataFormFactory.cs b/Extensions/XEP-0004/Dataforms/DataFormFactory.cs index 595471bc..7b65a3c2 100644 --- a/Extensions/XEP-0004/Dataforms/DataFormFactory.cs +++ b/Extensions/XEP-0004/Dataforms/DataFormFactory.cs @@ -22,32 +22,23 @@ internal static class DataFormFactory /// valid data-form element. public static DataForm Create(XmlElement element) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); if (element.Name != "x" || element.NamespaceURI != "jabber:x:data") throw new ArgumentException("Invalid root element: " + element.Name); string s = element.GetAttribute("type"); - if (String.IsNullOrEmpty(s)) + if (!(s?.Length > 0)) throw new ArgumentException("Missing 'type' attribute."); try { DataFormType type = Util.ParseEnum(s); - switch (type) + return type switch { - case DataFormType.Form: - return new RequestForm(element); - - case DataFormType.Submit: - return new SubmitForm(element); - - case DataFormType.Cancel: - return new CancelForm(element); - - case DataFormType.Result: - return new ResultForm(element); - - default: - throw new ArgumentException("Invalid form type: " + type); - } + DataFormType.Form => new RequestForm(element), + DataFormType.Submit => new SubmitForm(element), + DataFormType.Cancel => new CancelForm(element), + DataFormType.Result => new ResultForm(element), + _ => throw new ArgumentException("Invalid form type: " + type), + }; } catch (Exception e) { diff --git a/Extensions/XEP-0004/Dataforms/FieldList.cs b/Extensions/XEP-0004/Dataforms/FieldList.cs index c65245d0..908501a9 100644 --- a/Extensions/XEP-0004/Dataforms/FieldList.cs +++ b/Extensions/XEP-0004/Dataforms/FieldList.cs @@ -14,34 +14,17 @@ public class FieldList : IEnumerable, IReadOnlyCollection /// /// The underlying XML element of the data-form. /// - private XmlElement element; - - /// - /// Determines whether the collection of data-fields is read-only. - /// - private bool readOnly; + private readonly XmlElement element; /// /// Gets the number of elements contained in the list of data-fields. /// - public int Count - { - get - { - return GetFieldElements().Count; - } - } + public int Count => GetFieldElements().Count; /// /// Determines whether the FieldList is read-only. /// - public bool IsReadOnly - { - get - { - return readOnly; - } - } + public bool IsReadOnly { get; } /// /// Returns the data-field at the specified index. @@ -68,7 +51,7 @@ public DataField this[int index] /// The name of the data-field to return. /// The data-field with the specified name or null if no such /// data-field exists in the list of data-fields- - public DataField this[string name] + public DataField? this[string name] { get { @@ -94,10 +77,10 @@ public DataField this[string name] /// class is read-only. public void Add(DataField item) { - item.ThrowIfNull("item"); + item.ThrowIfNull(nameof(item)); if (IsReadOnly) throw new NotSupportedException("The list is read-only."); - if (item.Name != null && Contains(item.Name)) + if (item.Name.Length > 0 && Contains(item.Name)) throw new ArgumentException("A field with the same name already exists."); element.Child(item.ToXmlElement()); } @@ -113,9 +96,9 @@ public void Add(DataField item) /// null. public void Remove(DataField item) { - item.ThrowIfNull("item"); + item.ThrowIfNull(nameof(item)); // FIXME: This won't work for 'fixed' items that don't have names. - Remove(item.Name); + Remove(item!.Name); } /// @@ -128,7 +111,7 @@ public void Remove(string name) { if (name == null) return; - XmlElement e = GetFieldElementByName(name); + var e = GetFieldElementByName(name); if (e != null) element.RemoveChild(e); } @@ -161,7 +144,7 @@ public void Clear() /// null. public bool Contains(string name) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); foreach (var field in GetFields()) { if (field.Name == name) @@ -204,9 +187,9 @@ IEnumerator IEnumerable.GetEnumerator() /// valid XML data-form. public FieldList(XmlElement element, bool readOnly = false) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); this.element = element; - this.readOnly = readOnly; + this.IsReadOnly = readOnly; try { // Call GetFields to verify all fields are valid. @@ -264,49 +247,28 @@ private IList GetFields() /// valid data-field. private DataField FieldFromXml(XmlElement element) { - element.ThrowIfNull("element"); + element.ThrowIfNull(nameof(element)); try { // If the element does not have a 'type' attribute, we can only // return a weakly-typed data-field. DataFieldType? type = GetDataFieldType(element); - if (type.HasValue == false) - return new DataField(element); - switch (type.Value) - { - case DataFieldType.Boolean: - return new BooleanField(element); - - case DataFieldType.Fixed: - return new FixedField(element); - - case DataFieldType.TextSingle: - return new TextField(element); - - case DataFieldType.TextPrivate: - return new PasswordField(element); - - case DataFieldType.JidSingle: - return new JidField(element); - - case DataFieldType.Hidden: - return new HiddenField(element); - - case DataFieldType.TextMulti: - return new TextMultiField(element); - - case DataFieldType.JidMulti: - return new JidMultiField(element); - - case DataFieldType.ListMulti: - return new ListMultiField(element); - - case DataFieldType.ListSingle: - return new ListField(element); - - default: - throw new XmlException("Invalid 'type' attribute: " + type); - } + return !type.HasValue + ? new DataField(element) + : type.Value switch + { + DataFieldType.Boolean => new BooleanField(element), + DataFieldType.Fixed => new FixedField(element), + DataFieldType.TextSingle => new TextField(element), + DataFieldType.TextPrivate => new PasswordField(element), + DataFieldType.JidSingle => new JidField(element), + DataFieldType.Hidden => new HiddenField(element), + DataFieldType.TextMulti => new TextMultiField(element), + DataFieldType.JidMulti => new JidMultiField(element), + DataFieldType.ListMulti => new ListMultiField(element), + DataFieldType.ListSingle => new ListField(element), + _ => throw new XmlException("Invalid 'type' attribute: " + type), + }; } catch (Exception e) { @@ -322,12 +284,12 @@ private DataField FieldFromXml(XmlElement element) /// if no such element exists in the list of data-fields. /// The name parameter is /// null. - private XmlElement GetFieldElementByName(string name) + private XmlElement? GetFieldElementByName(string name) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); foreach (XmlElement e in GetFieldElements()) { - String s = e.GetAttribute("var"); + string s = e.GetAttribute("var"); if (s == name) return e; } @@ -348,13 +310,13 @@ private XmlElement GetFieldElementByName(string name) /// the named constants of the DataFieldType enumeration. private DataFieldType AttributeValueToType(string value) { - value.ThrowIfNull("value"); - StringBuilder b = new StringBuilder(); + value.ThrowIfNull(nameof(value)); + StringBuilder b = new(); string s = value; for (int i = 0; i < s.Length; i++) { if (s[i] == '-') - b.Append(Char.ToUpper(s[++i])); + b.Append(char.ToUpper(s[++i])); else b.Append(s[i]); } @@ -375,9 +337,7 @@ private DataFieldType AttributeValueToType(string value) try { string t = element.GetAttribute("type"); - if (String.IsNullOrEmpty(t)) - return null; - return AttributeValueToType(t); + return t?.Length > 0 ? (DataFieldType?)AttributeValueToType(t) : null; } catch (Exception e) { diff --git a/Extensions/XEP-0004/Dataforms/FixedField.cs b/Extensions/XEP-0004/Dataforms/FixedField.cs index 192e60db..da3c6058 100644 --- a/Extensions/XEP-0004/Dataforms/FixedField.cs +++ b/Extensions/XEP-0004/Dataforms/FixedField.cs @@ -16,22 +16,18 @@ public class FixedField : DataField /// /// The value of the field. /// - public string Value + public string? Value { - get - { - var v = element["value"]; - return v != null ? v.InnerText : null; - } + get => element["value"]?.InnerText; private set { - if (element["value"] != null) + if (element["value"] is { } node) { - if (value == null) - element.RemoveChild(element["value"]); + if (value is null) + element.RemoveChild(node); else - element["value"].InnerText = value; + node.InnerText = value; } else { @@ -52,11 +48,11 @@ private set /// The default value of the field. /// The name parameter is /// null. - public FixedField(string name, string label = null, string description = null, - string value = null) + public FixedField(string name, string? label = null, string? description = null, + string? value = null) : base(DataFieldType.Fixed, name, false, label, description) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); Value = value; } diff --git a/Extensions/XEP-0004/Dataforms/HiddenField.cs b/Extensions/XEP-0004/Dataforms/HiddenField.cs index 3c8043ad..8de9ee1f 100644 --- a/Extensions/XEP-0004/Dataforms/HiddenField.cs +++ b/Extensions/XEP-0004/Dataforms/HiddenField.cs @@ -17,18 +17,12 @@ public class HiddenField : DataField /// /// The values of the field. /// - private XmlCollection values; + private readonly XmlCollection values; /// /// Gets an enumerable collection of values set on the field. /// - public new ICollection Values - { - get - { - return values; - } - } + public new ICollection Values => values; /// /// Initializes a new instance of the HiddenField class for use in a @@ -43,8 +37,8 @@ public class HiddenField : DataField /// The default values of the field. /// The name parameter is /// null. - public HiddenField(string name, bool required = false, string label = null, - string description = null, params string[] values) + public HiddenField(string name, bool required = false, string? label = null, + string? description = null, params string[] values) : base(DataFieldType.Hidden, name, required, label, description) { this.values = new XmlCollection(element, "value", elem => elem.InnerText); @@ -87,6 +81,7 @@ internal HiddenField(XmlElement element) : base(element) { AssertType(DataFieldType.Hidden); + values = new XmlCollection(element, "value", elem => elem.InnerText); } } } \ No newline at end of file diff --git a/Extensions/XEP-0004/Dataforms/JidField.cs b/Extensions/XEP-0004/Dataforms/JidField.cs index da94d94d..a64fb4df 100644 --- a/Extensions/XEP-0004/Dataforms/JidField.cs +++ b/Extensions/XEP-0004/Dataforms/JidField.cs @@ -17,25 +17,22 @@ public class JidField : DataField /// /// The value of the underlying XML element /// is not a valid JID. - public Jid Jid + public Jid? Jid { - get - { - return GetJid(); - } + get => GetJid(); private set { - if (element["value"] != null) + if (element["value"] is { } node) { - if (value == null) - element.RemoveChild(element["value"]); + if (value is null) + element.RemoveChild(node); else - element["value"].InnerText = value.ToString(); + node.InnerText = value.ToString(); } else { - if (value != null) + if (value is not null) element.Child(Xml.Element("value").Text(value.ToString())); } } @@ -54,11 +51,11 @@ private set /// The default value of the field. /// The name parameter is /// null. - public JidField(string name, bool required = false, string label = null, - string description = null, Jid jid = null) + public JidField(string name, bool required = false, string? label = null, + string? description = null, Jid? jid = null) : base(DataFieldType.JidSingle, name, required, label, description) { - name.ThrowIfNull("name"); + name.ThrowIfNull(nameof(name)); Jid = jid; } @@ -107,12 +104,12 @@ internal JidField(XmlElement element) /// The gathered or provided JID. /// The value of the underlying XML element /// is not a valid JID. - private Jid GetJid() + private Jid? GetJid() { XmlElement v = element["value"]; try { - return v != null ? new Jid(v.InnerText) : null; + return v?.InnerText.Length > 0 ? new Jid(v.InnerText) : null; } catch (Exception e) { diff --git a/Extensions/XEP-0004/Dataforms/JidMultiField.cs b/Extensions/XEP-0004/Dataforms/JidMultiField.cs index 77d69a7a..2bcd4c7e 100644 --- a/Extensions/XEP-0004/Dataforms/JidMultiField.cs +++ b/Extensions/XEP-0004/Dataforms/JidMultiField.cs @@ -17,18 +17,12 @@ public class JidMultiField : DataField /// /// The values of the field. /// - private XmlCollection values; + private readonly XmlCollection values; /// /// Gets an enumerable collection of values set on the field. /// - public new ICollection Values - { - get - { - return values; - } - } + public new ICollection Values => values; /// /// Initializes a new instance of the JidMultiField class for use in a @@ -43,16 +37,16 @@ public class JidMultiField : DataField /// The default values of the field. /// The name parameter is /// null. - public JidMultiField(string name, bool required = false, string label = null, - string description = null, params Jid[] values) + public JidMultiField(string name, bool required = false, string? label = null, + string? description = null, params Jid[] values) : base(DataFieldType.TextMulti, name, required, label, description) { - this.values = new XmlCollection(element, "value", e => new Jid(element.InnerText)); + this.values = new XmlCollection(element, "value", e => new Jid(e.InnerText)); if (values != null) { foreach (Jid s in values) { - if (s == null) + if (s is null) continue; this.values.Add(s); } @@ -87,6 +81,7 @@ internal JidMultiField(XmlElement element) : base(element) { AssertType(DataFieldType.JidMulti); + values = new XmlCollection(element, "value", e => new Jid(e.InnerText)); // FIXME: Assert existing values are valid JIDs? } } diff --git a/Extensions/XEP-0004/Dataforms/ListField.cs b/Extensions/XEP-0004/Dataforms/ListField.cs index 3078d443..a435eec8 100644 --- a/Extensions/XEP-0004/Dataforms/ListField.cs +++ b/Extensions/XEP-0004/Dataforms/ListField.cs @@ -16,42 +16,32 @@ public class ListField : DataField /// /// The options of the field. /// - private XmlCollection