1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  * Copyright (c) 2003-2012 by AG-Software 											 *
3  * All Rights Reserved.																 *
4  * Contact information for AG-Software is available at http://www.ag-software.de	 *
5  *																					 *
6  * Licence:																			 *
7  * The agsXMPP SDK is released under a dual licence									 *
8  * agsXMPP can be used under either of two licences									 *
9  * 																					 *
10  * A commercial licence which is probably the most appropriate for commercial 		 *
11  * corporate use and closed source projects. 										 *
12  *																					 *
13  * The GNU Public License (GPL) is probably most appropriate for inclusion in		 *
14  * other open source projects.														 *
15  *																					 *
16  * See README.html for details.														 *
17  *																					 *
18  * For general enquiries visit our website at:										 *
19  * http://www.ag-software.de														 *
20  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21 
22 using System;
23 using System.Text;
24 using System.Collections;
25 
26 using agsXMPP.Exceptions;
27 using agsXMPP.Collections;
28 #if STRINGPREP
29 using agsXMPP.Idn;
30 #endif
31 
32 namespace agsXMPP
33 {
34 	/// <summary>
35 	/// Class for building and handling XMPP Id's (JID's)
36 	/// </summary>
37     public class Jid : IComparable, IEquatable<Jid>
38 
39 	{
40         /*
41         14 possible invalid forms of JIDs and some variations on valid JIDs with invalid lengths, viz:
42 
43         jidforms = [
44             "",
45             "@",
46             "@/resource",
47             "@domain",
48             "@domain/",
49             "@domain/resource",
50             "nodename@",
51             "/",
52             "nodename@domain/",
53             "nodename@/",
54             "@/",
55             "nodename/",
56             "/resource",
57             "nodename@/resource",
58         ]
59 
60 
61         TODO
62         Each allowable portion of a JID (node identifier, domain identifier, and resource identifier) MUST NOT
63         be more than 1023 bytes in length, resulting in a maximum total size
64         (including the '@' and '/' separators) of 3071 bytes.
65 
66         stringprep with libIDN
67         m_User      ==> nodeprep
68         m_Server    ==> nameprep
69         m_Resource  ==> resourceprep
70         */
71 
72         // !!!
73         // use this internal variables only if you know what you are doing
74         // !!!
75         internal string m_Jid      = null;
76 		internal string m_User     = null;
77 		internal string m_Server   = null;
78 		internal string m_Resource = null;
79 
80 		/// <summary>
81 		/// Create a new JID object from a string. The input string must be a valid jabberId and already prepared with stringprep.
82         /// Otherwise use one of the other constructors with escapes the node and prepares the gives balues with the stringprep
83         /// profiles
84 		/// </summary>
85 		/// <param name="jid">XMPP ID, in string form examples: user@server/Resource, user@server</param>
Jid(string jid)86 		public Jid(string jid)
87 		{
88 			m_Jid = jid;
89 			Parse(jid);
90 		}
91 
92         /// <summary>
93         /// builds a new Jid object
94         /// </summary>
95         /// <param name="user">XMPP User part</param>
96         /// <param name="server">XMPP Domain part</param>
97         /// <param name="resource">XMPP Resource part</param>
Jid(string user, string server, string resource)98         public Jid(string user, string server, string resource)
99         {
100 #if !STRINGPREP
101             if (user != null)
102             {
103                 user = EscapeNode(user);
104 
105                 m_User = user.ToLower();
106             }
107 
108             if (server != null)
109                 m_Server = server.ToLower();
110 
111             if (resource != null)
112                 m_Resource = resource;
113 #else
114             if (user != null)
115             {
116                 user = EscapeNode(user);
117 
118                 m_User = Stringprep.NodePrep(user);
119             }
120 
121             if (server != null)
122                 m_Server = Stringprep.NamePrep(server);
123 
124             if (resource != null)
125                 m_Resource = Stringprep.ResourcePrep(resource);
126 #endif
127             BuildJid();
128         }
129 
130         /// <summary>
131         /// Parses a JabberId from a string. If we parse a jid we assume it's correct and already prepared via stringprep.
132         /// </summary>
133         /// <param name="fullJid">jis to parse as string</param>
134         /// <returns>true if the jid could be parsed, false if an error occured</returns>
Parse(string fullJid)135 		public bool Parse(string fullJid)
136 		{
137 			string user		= null;
138 			string server	= null;
139 			string resource = null;
140 
141             try
142             {
143                 if (fullJid == null || fullJid.Length == 0)
144                 {
145                     return false;
146                 }
147 
148                 m_Jid = fullJid;
149 
150                 int atPos = m_Jid.IndexOf('@');
151                 int slashPos = m_Jid.IndexOf('/');
152 
153                 // some more validations
154                 // @... or /...
155                 if (atPos == 0 || slashPos == 0)
156                     return false;
157 
158                 // nodename@
159                 if (atPos + 1 == fullJid.Length)
160                     return false;
161 
162                 // @/ at followed by resource separator
163                 if (atPos + 1 == slashPos)
164                     return false;
165 
166                 if (atPos == -1)
167                 {
168                     user = null;
169                     if (slashPos == -1)
170                     {
171                         // JID Contains only the Server
172                         server = m_Jid;
173                     }
174                     else
175                     {
176                         // JID Contains only the Server and Resource
177                         server = m_Jid.Substring(0, slashPos);
178                         resource = m_Jid.Substring(slashPos + 1);
179                     }
180                 }
181                 else
182                 {
183                     if (slashPos == -1)
184                     {
185                         // We have no resource
186                         // Devide User and Server (user@server)
187                         server = m_Jid.Substring(atPos + 1);
188                         user = m_Jid.Substring(0, atPos);
189                     }
190                     else
191                     {
192                         // We have all
193                         user = m_Jid.Substring(0, atPos);
194                         server = m_Jid.Substring(atPos + 1, slashPos - atPos - 1);
195                         resource = m_Jid.Substring(slashPos + 1);
196                     }
197                 }
198 
199                 if (user != null)
200                     this.m_User = user;
201                 if (server != null)
202                     this.m_Server = server;
203                 if (resource != null)
204                     this.m_Resource = resource;
205 
206                 return true;
207             }
208             catch (Exception)
209             {
210                 return false;
211             }
212 		}
213 
BuildJid()214         internal void BuildJid()
215         {
216             m_Jid = BuildJid(m_User, m_Server, m_Resource);
217         }
218 
BuildJid(string user, string server, string resource)219         private string BuildJid(string user, string server, string resource)
220 		{
221 			StringBuilder sb = new StringBuilder();
222 			if (user != null)
223 			{
224 				sb.Append(user);
225 				sb.Append("@");
226 			}
227 			sb.Append(server);
228 			if (resource != null)
229 			{
230 				sb.Append("/");
231 				sb.Append(resource);
232 			}
233 			return sb.ToString();
234 		}
235 
ToString()236 		public override string ToString()
237 		{
238 			return m_Jid;
239 		}
240 
241 		/// <summary>
242 		/// the user part of the JabberId.
243 		/// </summary>
244         public string User
245 		{
246 			get
247 			{
248 				return m_User;
249 			}
250 			set
251 			{
252                 // first Encode the user/node
253                 string tmpUser = EscapeNode(value);
254 #if !STRINGPREP
255                 if (value != null)
256 				    m_User = tmpUser.ToLower();
257                 else
258                     m_User = null;
259 #else
260                 if (value != null)
261                     m_User = Stringprep.NodePrep(tmpUser);
262                 else
263                     m_User = null;
264 #endif
265                 BuildJid();
266 			}
267 		}
268 
269 		/// <summary>
270 		/// Only Server
271 		/// </summary>
272         public string Server
273 		{
274 			get
275 			{
276 				return m_Server;
277 			}
278 			set
279 			{
280 #if !STRINGPREP
281                 if (value != null)
282 				    m_Server = value.ToLower();
283                 else
284                     m_Server = null;
285 #else
286                 if (value != null)
287                     m_Server = Stringprep.NamePrep(value);
288                 else
289                     m_Server = null;
290 #endif
291                 BuildJid();
292 			}
293 		}
294 
295 		/// <summary>
296 		/// Only the Resource field.
297 		/// null for none
298 		/// </summary>
299         public string Resource
300 		{
301 			get
302 			{
303 				return m_Resource;
304 			}
305 			set
306 			{
307 #if !STRINGPREP
308                 if (value != null)
309 				    m_Resource = value;
310                 else
311                     m_Resource = null;
312 #else
313                 if (value != null)
314                     m_Resource = Stringprep.ResourcePrep(value);
315                 else
316                     m_Resource = null;
317 #endif
318                 BuildJid();
319 			}
320 		}
321 
322 		/// <summary>
323 		/// The Bare Jid only (user@server).
324 		/// </summary>
325         public string Bare
326 		{
327 			get
328 			{
329 				return BuildJid(m_User, m_Server, null);
330 			}
331         }
332 
333         #region << Overrides >>
334         /// <summary>
335         /// This compares the full Jid by default
336         /// </summary>
337         /// <param name="obj"></param>
338         /// <returns></returns>
Equals(object obj)339         public override bool Equals(object obj)
340         {
341             return Equals(obj, new FullJidComparer());
342         }
343 
GetHashCode()344         public override int GetHashCode()
345         {
346             int hcode = 0;
347             if (m_User  !=null)
348                 hcode ^= m_User.GetHashCode();
349 
350             if (m_Server != null)
351                 hcode ^= m_Server.GetHashCode();
352 
353             if (m_Resource != null)
354                 hcode ^= m_Resource.GetHashCode();
355 
356             return hcode;
357         }
358         #endregion
359 
Equals(object other, System.Collections.IComparer comparer)360         public bool Equals(object other, System.Collections.IComparer comparer)
361 		{
362 			if (comparer.Compare(other, this) == 0)
363 				return true;
364 			else
365 				return false;
366         }
367 
368         /*
369         public static bool operator !=(Jid jid1, Jid jid2)
370         {
371             return !jid1.Equals(jid2, new FullJidComparer());
372         }
373 
374         public static bool operator ==(Jid jid1, Jid jid2)
375         {
376             return jid1.Equals(jid2, new FullJidComparer());
377         }
378         */
379 
380         #region << implicit operators >>
operator Jid(string value)381         static public implicit operator Jid(string value)
382         {
383             if (value == null) {
384                 return null;
385             }
386             return new Jid(value);
387         }
388 
operator string(Jid jid)389         static public implicit operator string(Jid jid)
390         {
391             if (jid == null) {
392                 return null;
393             }
394             return jid.ToString();
395         }
396         #endregion
397 
398         #region IComparable Members
CompareTo(object obj)399         public int CompareTo(object obj)
400         {
401             if (obj is Jid)
402             {
403                 Jid jid = obj as Jid;
404                 FullJidComparer comparer = new FullJidComparer();
405                 return comparer.Compare(obj, this);
406             }
407             throw new ArgumentException("object is not a Jid");
408         }
409         #endregion
410 
411 
412         #region IEquatable<Jid> Members
Equals(Jid other)413         public bool Equals(Jid other)
414         {
415             FullJidComparer comparer = new FullJidComparer();
416             if (comparer.Compare(other, this) == 0)
417                 return true;
418             else
419                 return false;
420         }
421         #endregion
422 
423 
424         #region << XEP-0106: JID Escaping >>
425         /// <summary>
426         /// <para>
427         /// Escape a node according to XEP-0106
428         /// </para>
429         /// <para>
430         /// <a href="http://www.xmpp.org/extensions/xep-0106.html">http://www.xmpp.org/extensions/xep-0106.html</a>
431         /// </para>
432         /// </summary>
433         /// <param name="node"></param>
434         /// <returns></returns>
EscapeNode(string node)435         public static string EscapeNode(string node)
436         {
437             if (node == null)
438                 return null;
439 
440             StringBuilder sb = new StringBuilder();
441             for (int i = 0; i < node.Length; i++)
442             {
443                 /*
444                 <space> \20
445                 " 	    \22
446                 & 	    \26
447                 ' 	    \27
448                 / 	    \2f
449                 : 	    \3a
450                 < 	    \3c
451                 > 	    \3e
452                 @ 	    \40
453                 \ 	    \5c
454                 */
455                 char c = node[i];
456                 switch (c)
457                 {
458                     case ' ': sb.Append(@"\20"); break;
459                     case '"': sb.Append(@"\22"); break;
460                     case '&': sb.Append(@"\26"); break;
461                     case '\'': sb.Append(@"\27"); break;
462                     case '/': sb.Append(@"\2f"); break;
463                     case ':': sb.Append(@"\3a"); break;
464                     case '<': sb.Append(@"\3c"); break;
465                     case '>': sb.Append(@"\3e"); break;
466                     case '@': sb.Append(@"\40"); break;
467                     case '\\': sb.Append(@"\5c"); break;
468                     default: sb.Append(c); break;
469                 }
470             }
471             return sb.ToString();
472         }
473 
474         /// <summary>
475         /// <para>
476         /// unescape a node according to XEP-0106
477         /// </para>
478         /// <para>
479         /// <a href="http://www.xmpp.org/extensions/xep-0106.html">http://www.xmpp.org/extensions/xep-0106.html</a>
480         /// </para>
481         /// </summary>
482         /// <param name="node"></param>
483         /// <returns></returns>
UnescapeNode(string node)484         public static string UnescapeNode(string node)
485         {
486             if (node == null)
487                 return null;
488 
489             StringBuilder sb = new StringBuilder();
490             for (int i = 0; i < node.Length; i++)
491             {
492                 char c1 = node[i];
493                 if (c1 == '\\' && i + 2 < node.Length)
494                 {
495                     i += 1;
496                     char c2 = node[i];
497                     i += 1;
498                     char c3 = node[i];
499                     if (c2 == '2')
500                     {
501                         switch (c3)
502                         {
503                             case '0':
504                                 sb.Append(' ');
505                                 break;
506                             case '2':
507                                 sb.Append('"');
508                                 break;
509                             case '6':
510                                 sb.Append('&');
511                                 break;
512                             case '7':
513                                 sb.Append('\'');
514                                 break;
515                             case 'f':
516                                 sb.Append('/');
517                                 break;
518                         }
519                     }
520                     else if (c2 == '3')
521                     {
522                         switch (c3)
523                         {
524                             case 'a':
525                                 sb.Append(':');
526                                 break;
527                             case 'c':
528                                 sb.Append('<');
529                                 break;
530                             case 'e':
531                                 sb.Append('>');
532                                 break;
533                         }
534                     }
535                     else if (c2 == '4')
536                     {
537                         if (c3 == '0')
538                             sb.Append("@");
539                     }
540                     else if (c2 == '5')
541                     {
542                         if (c3 == 'c')
543                             sb.Append("\\");
544                     }
545                 }
546                 else
547                     sb.Append(c1);
548             }
549             return sb.ToString();
550         }
551 
552         #endregion
553     }
554 }