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 }