1 /* 2 * Conditions Of Use 3 * 4 * This software was developed by employees of the National Institute of 5 * Standards and Technology (NIST), an agency of the Federal Government. 6 * Pursuant to title 15 Untied States Code Section 105, works of NIST 7 * employees are not subject to copyright protection in the United States 8 * and are considered to be in the public domain. As a result, a formal 9 * license is not needed to use the software. 10 * 11 * This software is provided by NIST as a service and is expressly 12 * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED 13 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF 14 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT 15 * AND DATA ACCURACY. NIST does not warrant or make any representations 16 * regarding the use of the software or the results thereof, including but 17 * not limited to the correctness, accuracy, reliability or usefulness of 18 * the software. 19 * 20 * Permission to use this software is contingent upon your acceptance 21 * of the terms of this agreement 22 * 23 * . 24 * 25 */ 26 /******************************************************************************* 27 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD) * 28 ******************************************************************************/ 29 package gov.nist.javax.sip.message; 30 31 import gov.nist.core.InternalErrorHandler; 32 import gov.nist.javax.sip.SIPConstants; 33 import gov.nist.javax.sip.Utils; 34 import gov.nist.javax.sip.header.AlertInfo; 35 import gov.nist.javax.sip.header.Authorization; 36 import gov.nist.javax.sip.header.CSeq; 37 import gov.nist.javax.sip.header.CallID; 38 import gov.nist.javax.sip.header.Contact; 39 import gov.nist.javax.sip.header.ContactList; 40 import gov.nist.javax.sip.header.ContentLength; 41 import gov.nist.javax.sip.header.ContentType; 42 import gov.nist.javax.sip.header.ErrorInfo; 43 import gov.nist.javax.sip.header.ErrorInfoList; 44 import gov.nist.javax.sip.header.From; 45 import gov.nist.javax.sip.header.InReplyTo; 46 import gov.nist.javax.sip.header.MaxForwards; 47 import gov.nist.javax.sip.header.Priority; 48 import gov.nist.javax.sip.header.ProxyAuthenticate; 49 import gov.nist.javax.sip.header.ProxyAuthorization; 50 import gov.nist.javax.sip.header.ProxyRequire; 51 import gov.nist.javax.sip.header.ProxyRequireList; 52 import gov.nist.javax.sip.header.RSeq; 53 import gov.nist.javax.sip.header.RecordRouteList; 54 import gov.nist.javax.sip.header.RetryAfter; 55 import gov.nist.javax.sip.header.Route; 56 import gov.nist.javax.sip.header.RouteList; 57 import gov.nist.javax.sip.header.SIPETag; 58 import gov.nist.javax.sip.header.SIPHeader; 59 import gov.nist.javax.sip.header.SIPHeaderList; 60 import gov.nist.javax.sip.header.SIPHeaderNamesCache; 61 import gov.nist.javax.sip.header.SIPIfMatch; 62 import gov.nist.javax.sip.header.Server; 63 import gov.nist.javax.sip.header.Subject; 64 import gov.nist.javax.sip.header.To; 65 import gov.nist.javax.sip.header.Unsupported; 66 import gov.nist.javax.sip.header.UserAgent; 67 import gov.nist.javax.sip.header.Via; 68 import gov.nist.javax.sip.header.ViaList; 69 import gov.nist.javax.sip.header.WWWAuthenticate; 70 import gov.nist.javax.sip.header.Warning; 71 import gov.nist.javax.sip.parser.HeaderParser; 72 import gov.nist.javax.sip.parser.ParserFactory; 73 import gov.nist.javax.sip.parser.PipelinedMsgParser; 74 import gov.nist.javax.sip.parser.StringMsgParser; 75 76 import java.io.UnsupportedEncodingException; 77 import java.lang.reflect.Field; 78 import java.text.ParseException; 79 import java.util.Collection; 80 import java.util.Hashtable; 81 import java.util.Iterator; 82 import java.util.LinkedList; 83 import java.util.List; 84 import java.util.ListIterator; 85 import java.util.concurrent.ConcurrentLinkedQueue; 86 87 import javax.sip.InvalidArgumentException; 88 import javax.sip.SipException; 89 import javax.sip.header.AuthorizationHeader; 90 import javax.sip.header.CSeqHeader; 91 import javax.sip.header.CallIdHeader; 92 import javax.sip.header.ContactHeader; 93 import javax.sip.header.ContentDispositionHeader; 94 import javax.sip.header.ContentEncodingHeader; 95 import javax.sip.header.ContentLanguageHeader; 96 import javax.sip.header.ContentLengthHeader; 97 import javax.sip.header.ContentTypeHeader; 98 import javax.sip.header.ExpiresHeader; 99 import javax.sip.header.FromHeader; 100 import javax.sip.header.Header; 101 import javax.sip.header.MaxForwardsHeader; 102 import javax.sip.header.RecordRouteHeader; 103 import javax.sip.header.RouteHeader; 104 import javax.sip.header.ToHeader; 105 import javax.sip.header.ViaHeader; 106 import javax.sip.message.Request; 107 108 /* 109 * Acknowledgements: Yanick Belanger sent in a patch for the right content length when the content 110 * is a String. Bill Mccormick from Nortel Networks sent in a bug fix for setContent. 111 * 112 */ 113 /** 114 * This is the main SIP Message structure. 115 * 116 * @see StringMsgParser 117 * @see PipelinedMsgParser 118 * 119 * @version 1.2 $Revision: 1.53 $ $Date: 2009/12/16 14:58:40 $ 120 * @since 1.1 121 * 122 * @author M. Ranganathan <br/> 123 * 124 * 125 */ 126 public abstract class SIPMessage extends MessageObject implements javax.sip.message.Message, 127 MessageExt { 128 129 // JvB: use static here? 130 private String contentEncodingCharset = MessageFactoryImpl.getDefaultContentEncodingCharset(); 131 132 /* 133 * True if this is a null request. 134 */ 135 protected boolean nullRequest; 136 137 /** 138 * unparsed headers 139 */ 140 protected LinkedList<String> unrecognizedHeaders; 141 142 /** 143 * List of parsed headers (in the order they were added) 144 */ 145 protected ConcurrentLinkedQueue<SIPHeader> headers; 146 147 /** 148 * Direct accessors for frequently accessed headers 149 */ 150 protected From fromHeader; 151 152 protected To toHeader; 153 154 protected CSeq cSeqHeader; 155 156 protected CallID callIdHeader; 157 158 protected ContentLength contentLengthHeader; 159 160 protected MaxForwards maxForwardsHeader; 161 162 // Cumulative size of all the headers. 163 protected int size; 164 165 // Payload 166 private String messageContent; 167 168 private byte[] messageContentBytes; 169 170 private Object messageContentObject; 171 172 // Table of headers indexed by name. 173 private Hashtable<String, SIPHeader> nameTable; 174 175 /** 176 * The application data pointer. This is un-interpreted by the stack. This is provided as a 177 * convenient way of keeping book-keeping data for applications. 178 */ 179 protected Object applicationData; 180 181 /** 182 * Return true if the header belongs only in a Request. 183 * 184 * @param sipHeader is the header to test. 185 */ isRequestHeader(SIPHeader sipHeader)186 public static boolean isRequestHeader(SIPHeader sipHeader) { 187 return sipHeader instanceof AlertInfo || sipHeader instanceof InReplyTo 188 || sipHeader instanceof Authorization || sipHeader instanceof MaxForwards 189 || sipHeader instanceof UserAgent || sipHeader instanceof Priority 190 || sipHeader instanceof ProxyAuthorization || sipHeader instanceof ProxyRequire 191 || sipHeader instanceof ProxyRequireList || sipHeader instanceof Route 192 || sipHeader instanceof RouteList || sipHeader instanceof Subject 193 || sipHeader instanceof SIPIfMatch; 194 } 195 196 /** 197 * Return true if the header belongs only in a response. 198 * 199 * @param sipHeader is the header to test. 200 */ isResponseHeader(SIPHeader sipHeader)201 public static boolean isResponseHeader(SIPHeader sipHeader) { 202 return sipHeader instanceof ErrorInfo || sipHeader instanceof ProxyAuthenticate 203 || sipHeader instanceof Server || sipHeader instanceof Unsupported 204 || sipHeader instanceof RetryAfter || sipHeader instanceof Warning 205 || sipHeader instanceof WWWAuthenticate || sipHeader instanceof SIPETag 206 || sipHeader instanceof RSeq; 207 208 } 209 210 /** 211 * Get the headers as a linked list of encoded Strings 212 * 213 * @return a linked list with each element of the list containing a string encoded header in 214 * canonical form. 215 */ getMessageAsEncodedStrings()216 public LinkedList<String> getMessageAsEncodedStrings() { 217 LinkedList<String> retval = new LinkedList<String>(); 218 Iterator<SIPHeader> li = headers.iterator(); 219 while (li.hasNext()) { 220 SIPHeader sipHeader = (SIPHeader) li.next(); 221 if (sipHeader instanceof SIPHeaderList) { 222 SIPHeaderList< ? > shl = (SIPHeaderList< ? >) sipHeader; 223 retval.addAll(shl.getHeadersAsEncodedStrings()); 224 } else { 225 retval.add(sipHeader.encode()); 226 } 227 } 228 229 return retval; 230 } 231 232 /** 233 * Encode only the message and exclude the contents (for debugging); 234 * 235 * @return a string with all the headers encoded. 236 */ encodeSIPHeaders()237 protected String encodeSIPHeaders() { 238 StringBuffer encoding = new StringBuffer(); 239 Iterator<SIPHeader> it = this.headers.iterator(); 240 241 while (it.hasNext()) { 242 SIPHeader siphdr = (SIPHeader) it.next(); 243 if (!(siphdr instanceof ContentLength)) 244 siphdr.encode(encoding); 245 } 246 247 return contentLengthHeader.encode(encoding).append(NEWLINE).toString(); 248 } 249 250 /** 251 * Encode all the headers except the contents. For debug logging. 252 */ encodeMessage()253 public abstract String encodeMessage(); 254 255 /** 256 * Get A dialog identifier constructed from this messsage. This is an id that can be used to 257 * identify dialogs. 258 * 259 * @param isServerTransaction is a flag that indicates whether this is a server transaction. 260 */ getDialogId(boolean isServerTransaction)261 public abstract String getDialogId(boolean isServerTransaction); 262 263 /** 264 * Template match for SIP messages. The matchObj is a SIPMessage template to match against. 265 * This method allows you to do pattern matching with incoming SIP messages. Null matches wild 266 * card. 267 * 268 * @param other is the match template to match against. 269 * @return true if a match occured and false otherwise. 270 */ match(Object other)271 public boolean match(Object other) { 272 if (other == null) 273 return true; 274 if (!other.getClass().equals(this.getClass())) 275 return false; 276 SIPMessage matchObj = (SIPMessage) other; 277 Iterator<SIPHeader> li = matchObj.getHeaders(); 278 while (li.hasNext()) { 279 SIPHeader hisHeaders = (SIPHeader) li.next(); 280 List<SIPHeader> myHeaders = this.getHeaderList(hisHeaders.getHeaderName()); 281 282 // Could not find a header to match his header. 283 if (myHeaders == null || myHeaders.size() == 0) 284 return false; 285 286 if (hisHeaders instanceof SIPHeaderList) { 287 ListIterator< ? > outerIterator = ((SIPHeaderList< ? >) hisHeaders) 288 .listIterator(); 289 while (outerIterator.hasNext()) { 290 SIPHeader hisHeader = (SIPHeader) outerIterator.next(); 291 if (hisHeader instanceof ContentLength) 292 continue; 293 ListIterator< ? > innerIterator = myHeaders.listIterator(); 294 boolean found = false; 295 while (innerIterator.hasNext()) { 296 SIPHeader myHeader = (SIPHeader) innerIterator.next(); 297 if (myHeader.match(hisHeader)) { 298 found = true; 299 break; 300 } 301 } 302 if (!found) 303 return false; 304 } 305 } else { 306 SIPHeader hisHeader = hisHeaders; 307 ListIterator<SIPHeader> innerIterator = myHeaders.listIterator(); 308 boolean found = false; 309 while (innerIterator.hasNext()) { 310 SIPHeader myHeader = (SIPHeader) innerIterator.next(); 311 if (myHeader.match(hisHeader)) { 312 found = true; 313 break; 314 } 315 } 316 if (!found) 317 return false; 318 } 319 } 320 return true; 321 322 } 323 324 /** 325 * Merge a request with a template 326 * 327 * @param template -- template to merge with. 328 * 329 */ merge(Object template)330 public void merge(Object template) { 331 if (!template.getClass().equals(this.getClass())) 332 throw new IllegalArgumentException("Bad class " + template.getClass()); 333 SIPMessage templateMessage = (SIPMessage) template; 334 Object[] templateHeaders = templateMessage.headers.toArray(); 335 for (int i = 0; i < templateHeaders.length; i++) { 336 SIPHeader hdr = (SIPHeader) templateHeaders[i]; 337 String hdrName = hdr.getHeaderName(); 338 List<SIPHeader> myHdrs = this.getHeaderList(hdrName); 339 if (myHdrs == null) { 340 this.attachHeader(hdr); 341 } else { 342 ListIterator<SIPHeader> it = myHdrs.listIterator(); 343 while (it.hasNext()) { 344 SIPHeader sipHdr = (SIPHeader) it.next(); 345 sipHdr.merge(hdr); 346 } 347 } 348 } 349 350 } 351 352 /** 353 * Encode this message as a string. This is more efficient when the payload is a string 354 * (rather than a binary array of bytes). If the payload cannot be encoded as a UTF-8 string 355 * then it is simply ignored (will not appear in the encoded message). 356 * 357 * @return The Canonical String representation of the message (including the canonical string 358 * representation of the SDP payload if it exists). 359 */ encode()360 public String encode() { 361 StringBuffer encoding = new StringBuffer(); 362 Iterator<SIPHeader> it = this.headers.iterator(); 363 364 while (it.hasNext()) { 365 SIPHeader siphdr = (SIPHeader) it.next(); 366 if (!(siphdr instanceof ContentLength)) 367 encoding.append(siphdr.encode()); 368 } 369 // Append the unrecognized headers. Headers that are not 370 // recognized are passed through unchanged. 371 for (String unrecognized : this.unrecognizedHeaders) { 372 encoding.append(unrecognized).append(NEWLINE); 373 } 374 375 encoding.append(contentLengthHeader.encode()).append(NEWLINE); 376 377 if (this.messageContentObject != null) { 378 String mbody = this.getContent().toString(); 379 380 encoding.append(mbody); 381 } else if (this.messageContent != null || this.messageContentBytes != null) { 382 383 String content = null; 384 try { 385 if (messageContent != null) 386 content = messageContent; 387 else { 388 // JvB: Check for 'charset' parameter which overrides the default UTF-8 389 content = new String(messageContentBytes, getCharset() ); 390 } 391 } catch (UnsupportedEncodingException ex) { 392 InternalErrorHandler.handleException(ex); 393 } 394 395 encoding.append(content); 396 } 397 return encoding.toString(); 398 } 399 400 /** 401 * Encode the message as a byte array. Use this when the message payload is a binary byte 402 * array. 403 * 404 * @return The Canonical byte array representation of the message (including the canonical 405 * byte array representation of the SDP payload if it exists all in one contiguous 406 * byte array). 407 */ encodeAsBytes(String transport)408 public byte[] encodeAsBytes(String transport) { 409 if (this instanceof SIPRequest && ((SIPRequest) this).isNullRequest()) { 410 return "\r\n\r\n".getBytes(); 411 } 412 // JvB: added to fix case where application provides the wrong transport 413 // in the topmost Via header 414 ViaHeader topVia = (ViaHeader) this.getHeader(ViaHeader.NAME); 415 try { 416 topVia.setTransport(transport); 417 } catch (ParseException e) { 418 InternalErrorHandler.handleException(e); 419 } 420 421 StringBuffer encoding = new StringBuffer(); 422 synchronized (this.headers) { 423 Iterator<SIPHeader> it = this.headers.iterator(); 424 425 while (it.hasNext()) { 426 SIPHeader siphdr = (SIPHeader) it.next(); 427 if (!(siphdr instanceof ContentLength)) 428 siphdr.encode(encoding); 429 430 } 431 } 432 contentLengthHeader.encode(encoding); 433 encoding.append(NEWLINE); 434 435 byte[] retval = null; 436 byte[] content = this.getRawContent(); 437 if (content != null) { 438 // Append the content 439 440 byte[] msgarray = null; 441 try { 442 msgarray = encoding.toString().getBytes( getCharset() ); 443 } catch (UnsupportedEncodingException ex) { 444 InternalErrorHandler.handleException(ex); 445 } 446 447 retval = new byte[msgarray.length + content.length]; 448 System.arraycopy(msgarray, 0, retval, 0, msgarray.length); 449 System.arraycopy(content, 0, retval, msgarray.length, content.length); 450 } else { 451 // Message content does not exist. 452 453 try { 454 retval = encoding.toString().getBytes( getCharset() ); 455 } catch (UnsupportedEncodingException ex) { 456 InternalErrorHandler.handleException(ex); 457 } 458 } 459 return retval; 460 } 461 462 /** 463 * clone this message (create a new deep physical copy). All headers in the message are 464 * cloned. You can modify the cloned copy without affecting the original. The content is 465 * handled as follows: If the content is a String, or a byte array, a new copy of the content 466 * is allocated and copied over. If the content is an Object that supports the clone method, 467 * then the clone method is invoked and the cloned content is the new content. Otherwise, the 468 * content of the new message is set equal to the old one. 469 * 470 * @return A cloned copy of this object. 471 */ clone()472 public Object clone() { 473 SIPMessage retval = (SIPMessage) super.clone(); 474 retval.nameTable = new Hashtable<String, SIPHeader>(); 475 retval.fromHeader = null; 476 retval.toHeader = null; 477 retval.cSeqHeader = null; 478 retval.callIdHeader = null; 479 retval.contentLengthHeader = null; 480 retval.maxForwardsHeader = null; 481 if (this.headers != null) { 482 retval.headers = new ConcurrentLinkedQueue<SIPHeader>(); 483 for (Iterator<SIPHeader> iter = headers.iterator(); iter.hasNext();) { 484 SIPHeader hdr = (SIPHeader) iter.next(); 485 retval.attachHeader((SIPHeader) hdr.clone()); 486 } 487 488 } 489 if (this.messageContentBytes != null) 490 retval.messageContentBytes = (byte[]) this.messageContentBytes.clone(); 491 if (this.messageContentObject != null) 492 retval.messageContentObject = makeClone(messageContentObject); 493 retval.unrecognizedHeaders = this.unrecognizedHeaders; 494 return retval; 495 } 496 497 /** 498 * Get the string representation of this header (for pretty printing the generated structure). 499 * 500 * @return Formatted string representation of the object. Note that this is NOT the same as 501 * encode(). This is used mainly for debugging purposes. 502 */ debugDump()503 public String debugDump() { 504 stringRepresentation = ""; 505 sprint("SIPMessage:"); 506 sprint("{"); 507 try { 508 509 Field[] fields = this.getClass().getDeclaredFields(); 510 for (int i = 0; i < fields.length; i++) { 511 Field f = fields[i]; 512 Class< ? > fieldType = f.getType(); 513 String fieldName = f.getName(); 514 if (f.get(this) != null && SIPHeader.class.isAssignableFrom(fieldType) 515 && fieldName.compareTo("headers") != 0) { 516 sprint(fieldName + "="); 517 sprint(((SIPHeader) f.get(this)).debugDump()); 518 } 519 } 520 } catch (Exception ex) { 521 InternalErrorHandler.handleException(ex); 522 } 523 524 sprint("List of headers : "); 525 sprint(headers.toString()); 526 sprint("messageContent = "); 527 sprint("{"); 528 sprint(messageContent); 529 sprint("}"); 530 if (this.getContent() != null) { 531 sprint(this.getContent().toString()); 532 } 533 sprint("}"); 534 return stringRepresentation; 535 } 536 537 /** 538 * Constructor: Initializes lists and list headers. All the headers for which there can be 539 * multiple occurances in a message are derived from the SIPHeaderListClass. All singleton 540 * headers are derived from SIPHeader class. 541 */ SIPMessage()542 public SIPMessage() { 543 this.unrecognizedHeaders = new LinkedList<String>(); 544 this.headers = new ConcurrentLinkedQueue<SIPHeader>(); 545 nameTable = new Hashtable<String, SIPHeader>(); 546 try { 547 this.attachHeader(new ContentLength(0), false); 548 } catch (Exception ex) { 549 } 550 } 551 552 /** 553 * Attach a header and die if you get a duplicate header exception. 554 * 555 * @param h SIPHeader to attach. 556 */ attachHeader(SIPHeader h)557 private void attachHeader(SIPHeader h) { 558 if (h == null) 559 throw new IllegalArgumentException("null header!"); 560 try { 561 if (h instanceof SIPHeaderList) { 562 SIPHeaderList< ? > hl = (SIPHeaderList< ? >) h; 563 if (hl.isEmpty()) { 564 return; 565 } 566 } 567 attachHeader(h, false, false); 568 } catch (SIPDuplicateHeaderException ex) { 569 // InternalErrorHandler.handleException(ex); 570 } 571 } 572 573 /** 574 * Attach a header (replacing the original header). 575 * 576 * @param sipHeader SIPHeader that replaces a header of the same type. 577 */ setHeader(Header sipHeader)578 public void setHeader(Header sipHeader) { 579 SIPHeader header = (SIPHeader) sipHeader; 580 if (header == null) 581 throw new IllegalArgumentException("null header!"); 582 try { 583 if (header instanceof SIPHeaderList) { 584 SIPHeaderList< ? > hl = (SIPHeaderList< ? >) header; 585 // Ignore empty lists. 586 if (hl.isEmpty()) 587 return; 588 } 589 this.removeHeader(header.getHeaderName()); 590 attachHeader(header, true, false); 591 } catch (SIPDuplicateHeaderException ex) { 592 InternalErrorHandler.handleException(ex); 593 } 594 } 595 596 /** 597 * Set a header from a linked list of headers. 598 * 599 * @param headers -- a list of headers to set. 600 */ setHeaders(java.util.List<SIPHeader> headers)601 public void setHeaders(java.util.List<SIPHeader> headers) { 602 ListIterator<SIPHeader> listIterator = headers.listIterator(); 603 while (listIterator.hasNext()) { 604 SIPHeader sipHeader = (SIPHeader) listIterator.next(); 605 try { 606 this.attachHeader(sipHeader, false); 607 } catch (SIPDuplicateHeaderException ex) { 608 } 609 } 610 } 611 612 /** 613 * Attach a header to the end of the existing headers in this SIPMessage structure. This is 614 * equivalent to the attachHeader(SIPHeader,replaceflag,false); which is the normal way in 615 * which headers are attached. This was added in support of JAIN-SIP. 616 * 617 * @param h header to attach. 618 * @param replaceflag if true then replace a header if it exists. 619 * @throws SIPDuplicateHeaderException If replaceFlag is false and only a singleton header is 620 * allowed (fpr example CSeq). 621 */ attachHeader(SIPHeader h, boolean replaceflag)622 public void attachHeader(SIPHeader h, boolean replaceflag) throws SIPDuplicateHeaderException { 623 this.attachHeader(h, replaceflag, false); 624 } 625 626 /** 627 * Attach the header to the SIP Message structure at a specified position in its list of 628 * headers. 629 * 630 * @param header Header to attach. 631 * @param replaceFlag If true then replace the existing header. 632 * @param top Location in the header list to insert the header. 633 * @exception SIPDuplicateHeaderException if the header is of a type that cannot tolerate 634 * duplicates and one of this type already exists (e.g. CSeq header). 635 * @throws IndexOutOfBoundsException If the index specified is greater than the number of 636 * headers that are in this message. 637 */ 638 attachHeader(SIPHeader header, boolean replaceFlag, boolean top)639 public void attachHeader(SIPHeader header, boolean replaceFlag, boolean top) 640 throws SIPDuplicateHeaderException { 641 if (header == null) { 642 throw new NullPointerException("null header"); 643 } 644 645 SIPHeader h; 646 647 if (ListMap.hasList(header) && !SIPHeaderList.class.isAssignableFrom(header.getClass())) { 648 SIPHeaderList<SIPHeader> hdrList = ListMap.getList(header); 649 hdrList.add(header); 650 h = hdrList; 651 } else { 652 h = header; 653 } 654 655 String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(h.getName()); 656 if (replaceFlag) { 657 nameTable.remove(headerNameLowerCase); 658 } else if (nameTable.containsKey(headerNameLowerCase) && !(h instanceof SIPHeaderList)) { 659 if (h instanceof ContentLength) { 660 try { 661 ContentLength cl = (ContentLength) h; 662 contentLengthHeader.setContentLength(cl.getContentLength()); 663 } catch (InvalidArgumentException e) { 664 } 665 } 666 // Just ignore duplicate header. 667 return; 668 } 669 670 SIPHeader originalHeader = (SIPHeader) getHeader(header.getName()); 671 672 // Delete the original header from our list structure. 673 if (originalHeader != null) { 674 Iterator<SIPHeader> li = headers.iterator(); 675 while (li.hasNext()) { 676 SIPHeader next = (SIPHeader) li.next(); 677 if (next.equals(originalHeader)) { 678 li.remove(); 679 } 680 } 681 } 682 683 if (!nameTable.containsKey(headerNameLowerCase)) { 684 nameTable.put(headerNameLowerCase, h); 685 headers.add(h); 686 } else { 687 if (h instanceof SIPHeaderList) { 688 SIPHeaderList< ? > hdrlist = (SIPHeaderList< ? >) nameTable 689 .get(headerNameLowerCase); 690 if (hdrlist != null) 691 hdrlist.concatenate((SIPHeaderList) h, top); 692 else 693 nameTable.put(headerNameLowerCase, h); 694 } else { 695 nameTable.put(headerNameLowerCase, h); 696 } 697 } 698 699 // Direct accessor fields for frequently accessed headers. 700 if (h instanceof From) { 701 this.fromHeader = (From) h; 702 } else if (h instanceof ContentLength) { 703 this.contentLengthHeader = (ContentLength) h; 704 } else if (h instanceof To) { 705 this.toHeader = (To) h; 706 } else if (h instanceof CSeq) { 707 this.cSeqHeader = (CSeq) h; 708 } else if (h instanceof CallID) { 709 this.callIdHeader = (CallID) h; 710 } else if (h instanceof MaxForwards) { 711 this.maxForwardsHeader = (MaxForwards) h; 712 } 713 714 } 715 716 /** 717 * Remove a header given its name. If multiple headers of a given name are present then the 718 * top flag determines which end to remove headers from. 719 * 720 * @param headerName is the name of the header to remove. 721 * @param top -- flag that indicates which end of header list to process. 722 */ removeHeader(String headerName, boolean top)723 public void removeHeader(String headerName, boolean top) { 724 725 String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName); 726 SIPHeader toRemove = (SIPHeader) nameTable.get(headerNameLowerCase); 727 // nothing to do then we are done. 728 if (toRemove == null) 729 return; 730 if (toRemove instanceof SIPHeaderList) { 731 SIPHeaderList< ? > hdrList = (SIPHeaderList< ? >) toRemove; 732 if (top) 733 hdrList.removeFirst(); 734 else 735 hdrList.removeLast(); 736 // Clean up empty list 737 if (hdrList.isEmpty()) { 738 Iterator<SIPHeader> li = this.headers.iterator(); 739 while (li.hasNext()) { 740 SIPHeader sipHeader = (SIPHeader) li.next(); 741 if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase)) 742 li.remove(); 743 } 744 745 // JvB: also remove it from the nameTable! Else NPE in 746 // DefaultRouter 747 nameTable.remove(headerNameLowerCase); 748 } 749 } else { 750 this.nameTable.remove(headerNameLowerCase); 751 if (toRemove instanceof From) { 752 this.fromHeader = null; 753 } else if (toRemove instanceof To) { 754 this.toHeader = null; 755 } else if (toRemove instanceof CSeq) { 756 this.cSeqHeader = null; 757 } else if (toRemove instanceof CallID) { 758 this.callIdHeader = null; 759 } else if (toRemove instanceof MaxForwards) { 760 this.maxForwardsHeader = null; 761 } else if (toRemove instanceof ContentLength) { 762 this.contentLengthHeader = null; 763 } 764 Iterator<SIPHeader> li = this.headers.iterator(); 765 while (li.hasNext()) { 766 SIPHeader sipHeader = (SIPHeader) li.next(); 767 if (sipHeader.getName().equalsIgnoreCase(headerName)) 768 li.remove(); 769 } 770 } 771 772 } 773 774 /** 775 * Remove all headers given its name. 776 * 777 * @param headerName is the name of the header to remove. 778 */ removeHeader(String headerName)779 public void removeHeader(String headerName) { 780 781 if (headerName == null) 782 throw new NullPointerException("null arg"); 783 String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName); 784 SIPHeader removed = (SIPHeader) nameTable.remove(headerNameLowerCase); 785 // nothing to do then we are done. 786 if (removed == null) 787 return; 788 789 // Remove the fast accessor fields. 790 if (removed instanceof From) { 791 this.fromHeader = null; 792 } else if (removed instanceof To) { 793 this.toHeader = null; 794 } else if (removed instanceof CSeq) { 795 this.cSeqHeader = null; 796 } else if (removed instanceof CallID) { 797 this.callIdHeader = null; 798 } else if (removed instanceof MaxForwards) { 799 this.maxForwardsHeader = null; 800 } else if (removed instanceof ContentLength) { 801 this.contentLengthHeader = null; 802 } 803 804 Iterator<SIPHeader> li = this.headers.iterator(); 805 while (li.hasNext()) { 806 SIPHeader sipHeader = (SIPHeader) li.next(); 807 if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase)) 808 li.remove(); 809 810 } 811 } 812 813 /** 814 * Generate (compute) a transaction ID for this SIP message. 815 * 816 * @return A string containing the concatenation of various portions of the From,To,Via and 817 * RequestURI portions of this message as specified in RFC 2543: All responses to a 818 * request contain the same values in the Call-ID, CSeq, To, and From fields (with the 819 * possible addition of a tag in the To field (section 10.43)). This allows responses 820 * to be matched with requests. Incorporates a bug fix for a bug sent in by Gordon 821 * Ledgard of IPera for generating transactionIDs when no port is present in the via 822 * header. Incorporates a bug fix for a bug report sent in by Chris Mills of Nortel 823 * Networks (converts to lower case when returning the transaction identifier). 824 * 825 * @return a string that can be used as a transaction identifier for this message. This can be 826 * used for matching responses and requests (i.e. an outgoing request and its matching 827 * response have the same computed transaction identifier). 828 */ getTransactionId()829 public String getTransactionId() { 830 Via topVia = null; 831 if (!this.getViaHeaders().isEmpty()) { 832 topVia = (Via) this.getViaHeaders().getFirst(); 833 } 834 // Have specified a branch Identifier so we can use it to identify 835 // the transaction. BranchId is not case sensitive. 836 // Branch Id prefix is not case sensitive. 837 if (topVia != null 838 && topVia.getBranch() != null 839 && topVia.getBranch().toUpperCase().startsWith( 840 SIPConstants.BRANCH_MAGIC_COOKIE_UPPER_CASE)) { 841 // Bis 09 compatible branch assignment algorithm. 842 // implies that the branch id can be used as a transaction 843 // identifier. 844 if (this.getCSeq().getMethod().equals(Request.CANCEL)) 845 return (topVia.getBranch() + ":" + this.getCSeq().getMethod()).toLowerCase(); 846 else 847 return topVia.getBranch().toLowerCase(); 848 } else { 849 // Old style client so construct the transaction identifier 850 // from various fields of the request. 851 StringBuffer retval = new StringBuffer(); 852 From from = (From) this.getFrom(); 853 To to = (To) this.getTo(); 854 // String hpFrom = from.getUserAtHostPort(); 855 // retval.append(hpFrom).append(":"); 856 if (from.hasTag()) 857 retval.append(from.getTag()).append("-"); 858 // String hpTo = to.getUserAtHostPort(); 859 // retval.append(hpTo).append(":"); 860 String cid = this.callIdHeader.getCallId(); 861 retval.append(cid).append("-"); 862 retval.append(this.cSeqHeader.getSequenceNumber()).append("-").append( 863 this.cSeqHeader.getMethod()); 864 if (topVia != null) { 865 retval.append("-").append(topVia.getSentBy().encode()); 866 if (!topVia.getSentBy().hasPort()) { 867 retval.append("-").append(5060); 868 } 869 } 870 if (this.getCSeq().getMethod().equals(Request.CANCEL)) { 871 retval.append(Request.CANCEL); 872 } 873 return retval.toString().toLowerCase().replace(":", "-").replace("@", "-") 874 + Utils.getSignature(); 875 } 876 } 877 878 /** 879 * Override the hashcode method ( see issue # 55 ) Note that if you try to use this method 880 * before you assemble a valid request, you will get a constant ( -1 ). Beware of placing any 881 * half formed requests in a table. 882 */ hashCode()883 public int hashCode() { 884 if (this.callIdHeader == null) 885 throw new RuntimeException( 886 "Invalid message! Cannot compute hashcode! call-id header is missing !"); 887 else 888 return this.callIdHeader.getCallId().hashCode(); 889 } 890 891 /** 892 * Return true if this message has a body. 893 */ hasContent()894 public boolean hasContent() { 895 return messageContent != null || messageContentBytes != null; 896 } 897 898 /** 899 * Return an iterator for the list of headers in this message. 900 * 901 * @return an Iterator for the headers of this message. 902 */ getHeaders()903 public Iterator<SIPHeader> getHeaders() { 904 return headers.iterator(); 905 } 906 907 /** 908 * Get the first header of the given name. 909 * 910 * @return header -- the first header of the given name. 911 */ getHeader(String headerName)912 public Header getHeader(String headerName) { 913 return getHeaderLowerCase(SIPHeaderNamesCache.toLowerCase(headerName)); 914 } 915 getHeaderLowerCase(String lowerCaseHeaderName)916 private Header getHeaderLowerCase(String lowerCaseHeaderName) { 917 if (lowerCaseHeaderName == null) 918 throw new NullPointerException("bad name"); 919 SIPHeader sipHeader = (SIPHeader) nameTable.get(lowerCaseHeaderName); 920 if (sipHeader instanceof SIPHeaderList) 921 return (Header) ((SIPHeaderList) sipHeader).getFirst(); 922 else 923 return (Header) sipHeader; 924 } 925 926 /** 927 * Get the contentType header (null if one does not exist). 928 * 929 * @return contentType header 930 */ 931 getContentTypeHeader()932 public ContentType getContentTypeHeader() { 933 return (ContentType) getHeaderLowerCase(CONTENT_TYPE_LOWERCASE); 934 } 935 936 private static final String CONTENT_TYPE_LOWERCASE = SIPHeaderNamesCache 937 .toLowerCase(ContentTypeHeader.NAME); 938 939 940 /** 941 * Get the contentLength header. 942 */ getContentLengthHeader()943 public ContentLengthHeader getContentLengthHeader() { 944 return this.getContentLength(); 945 } 946 947 948 /** 949 * Get the from header. 950 * 951 * @return -- the from header. 952 */ getFrom()953 public FromHeader getFrom() { 954 return (FromHeader) fromHeader; 955 } 956 957 /** 958 * Get the ErrorInfo list of headers (null if one does not exist). 959 * 960 * @return List containing ErrorInfo headers. 961 */ getErrorInfoHeaders()962 public ErrorInfoList getErrorInfoHeaders() { 963 return (ErrorInfoList) getSIPHeaderListLowerCase(ERROR_LOWERCASE); 964 } 965 966 private static final String ERROR_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ErrorInfo.NAME); 967 968 /** 969 * Get the Contact list of headers (null if one does not exist). 970 * 971 * @return List containing Contact headers. 972 */ getContactHeaders()973 public ContactList getContactHeaders() { 974 return (ContactList) this.getSIPHeaderListLowerCase(CONTACT_LOWERCASE); 975 } 976 977 private static final String CONTACT_LOWERCASE = SIPHeaderNamesCache 978 .toLowerCase(ContactHeader.NAME); 979 980 /** 981 * Get the contact header ( the first contact header) which is all we need for the most part. 982 * 983 */ getContactHeader()984 public Contact getContactHeader() { 985 ContactList clist = this.getContactHeaders(); 986 if (clist != null) { 987 return (Contact) clist.getFirst(); 988 989 } else { 990 return null; 991 } 992 } 993 994 /** 995 * Get the Via list of headers (null if one does not exist). 996 * 997 * @return List containing Via headers. 998 */ getViaHeaders()999 public ViaList getViaHeaders() { 1000 return (ViaList) getSIPHeaderListLowerCase(VIA_LOWERCASE); 1001 } 1002 1003 private static final String VIA_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ViaHeader.NAME); 1004 1005 /** 1006 * Set A list of via headers. 1007 * 1008 * @param viaList a list of via headers to add. 1009 */ setVia(java.util.List viaList)1010 public void setVia(java.util.List viaList) { 1011 ViaList vList = new ViaList(); 1012 ListIterator it = viaList.listIterator(); 1013 while (it.hasNext()) { 1014 Via via = (Via) it.next(); 1015 vList.add(via); 1016 } 1017 this.setHeader(vList); 1018 } 1019 1020 /** 1021 * Set the header given a list of headers. 1022 * 1023 * @param sipHeaderList a headerList to set 1024 */ 1025 setHeader(SIPHeaderList<Via> sipHeaderList)1026 public void setHeader(SIPHeaderList<Via> sipHeaderList) { 1027 this.setHeader((Header) sipHeaderList); 1028 } 1029 1030 /** 1031 * Get the topmost via header. 1032 * 1033 * @return the top most via header if one exists or null if none exists. 1034 */ getTopmostVia()1035 public Via getTopmostVia() { 1036 if (this.getViaHeaders() == null) 1037 return null; 1038 else 1039 return (Via) (getViaHeaders().getFirst()); 1040 } 1041 1042 /** 1043 * Get the CSeq list of header (null if one does not exist). 1044 * 1045 * @return CSeq header 1046 */ getCSeq()1047 public CSeqHeader getCSeq() { 1048 return (CSeqHeader) cSeqHeader; 1049 } 1050 1051 /** 1052 * Get the Authorization header (null if one does not exist). 1053 * 1054 * @return Authorization header. 1055 */ getAuthorization()1056 public Authorization getAuthorization() { 1057 return (Authorization) getHeaderLowerCase(AUTHORIZATION_LOWERCASE); 1058 } 1059 1060 private static final String AUTHORIZATION_LOWERCASE = SIPHeaderNamesCache 1061 .toLowerCase(AuthorizationHeader.NAME); 1062 1063 /** 1064 * Get the MaxForwards header (null if one does not exist). 1065 * 1066 * @return Max-Forwards header 1067 */ 1068 getMaxForwards()1069 public MaxForwardsHeader getMaxForwards() { 1070 return maxForwardsHeader; 1071 } 1072 1073 /** 1074 * Set the max forwards header. 1075 * 1076 * @param maxForwards is the MaxForwardsHeader to set. 1077 */ setMaxForwards(MaxForwardsHeader maxForwards)1078 public void setMaxForwards(MaxForwardsHeader maxForwards) { 1079 this.setHeader(maxForwards); 1080 } 1081 1082 /** 1083 * Get the Route List of headers (null if one does not exist). 1084 * 1085 * @return List containing Route headers 1086 */ getRouteHeaders()1087 public RouteList getRouteHeaders() { 1088 return (RouteList) getSIPHeaderListLowerCase(ROUTE_LOWERCASE); 1089 } 1090 1091 private static final String ROUTE_LOWERCASE = SIPHeaderNamesCache 1092 .toLowerCase(RouteHeader.NAME); 1093 1094 /** 1095 * Get the CallID header (null if one does not exist) 1096 * 1097 * @return Call-ID header . 1098 */ getCallId()1099 public CallIdHeader getCallId() { 1100 return callIdHeader; 1101 } 1102 1103 /** 1104 * Set the call id header. 1105 * 1106 * @param callId call idHeader (what else could it be?) 1107 */ setCallId(CallIdHeader callId)1108 public void setCallId(CallIdHeader callId) { 1109 this.setHeader(callId); 1110 } 1111 1112 /** 1113 * Get the CallID header (null if one does not exist) 1114 * 1115 * @param callId -- the call identifier to be assigned to the call id header 1116 */ setCallId(String callId)1117 public void setCallId(String callId) throws java.text.ParseException { 1118 if (callIdHeader == null) { 1119 this.setHeader(new CallID()); 1120 } 1121 callIdHeader.setCallId(callId); 1122 } 1123 1124 /** 1125 * Get the RecordRoute header list (null if one does not exist). 1126 * 1127 * @return Record-Route header 1128 */ getRecordRouteHeaders()1129 public RecordRouteList getRecordRouteHeaders() { 1130 return (RecordRouteList) this.getSIPHeaderListLowerCase(RECORDROUTE_LOWERCASE); 1131 } 1132 1133 private static final String RECORDROUTE_LOWERCASE = SIPHeaderNamesCache 1134 .toLowerCase(RecordRouteHeader.NAME); 1135 1136 /** 1137 * Get the To header (null if one does not exist). 1138 * 1139 * @return To header 1140 */ getTo()1141 public ToHeader getTo() { 1142 return (ToHeader) toHeader; 1143 } 1144 setTo(ToHeader to)1145 public void setTo(ToHeader to) { 1146 this.setHeader(to); 1147 } 1148 setFrom(FromHeader from)1149 public void setFrom(FromHeader from) { 1150 this.setHeader(from); 1151 1152 } 1153 1154 /** 1155 * Get the ContentLength header (null if one does not exist). 1156 * 1157 * @return content-length header. 1158 */ getContentLength()1159 public ContentLengthHeader getContentLength() { 1160 return this.contentLengthHeader; 1161 } 1162 1163 /** 1164 * Get the message body as a string. If the message contains a content type header with a 1165 * specified charset, and if the payload has been read as a byte array, then it is returned 1166 * encoded into this charset. 1167 * 1168 * @return Message body (as a string) 1169 * @throws UnsupportedEncodingException if the platform does not support the charset specified 1170 * in the content type header. 1171 * 1172 */ getMessageContent()1173 public String getMessageContent() throws UnsupportedEncodingException { 1174 if (this.messageContent == null && this.messageContentBytes == null) 1175 return null; 1176 else if (this.messageContent == null) { 1177 this.messageContent = new String(messageContentBytes, getCharset() ); 1178 } 1179 return this.messageContent; 1180 } 1181 1182 /** 1183 * Get the message content as an array of bytes. If the payload has been read as a String then 1184 * it is decoded using the charset specified in the content type header if it exists. 1185 * Otherwise, it is encoded using the default encoding which is UTF-8. 1186 * 1187 * @return an array of bytes that is the message payload. 1188 */ getRawContent()1189 public byte[] getRawContent() { 1190 try { 1191 if ( this.messageContentBytes != null ) { 1192 // return messageContentBytes; 1193 } else if (this.messageContentObject != null) { 1194 String messageContent = this.messageContentObject.toString(); 1195 this.messageContentBytes = messageContent.getBytes( getCharset() ); 1196 } else if (this.messageContent != null) { 1197 this.messageContentBytes = messageContent.getBytes( getCharset() ); 1198 } 1199 return this.messageContentBytes; 1200 } catch (UnsupportedEncodingException ex) { 1201 InternalErrorHandler.handleException(ex); 1202 return null; 1203 } 1204 } 1205 1206 /** 1207 * Set the message content given type and subtype. 1208 * 1209 * @param type is the message type (eg. application) 1210 * @param subType is the message sybtype (eg. sdp) 1211 * @param messageContent is the messge content as a string. 1212 */ setMessageContent(String type, String subType, String messageContent)1213 public void setMessageContent(String type, String subType, String messageContent) { 1214 if (messageContent == null) 1215 throw new IllegalArgumentException("messgeContent is null"); 1216 ContentType ct = new ContentType(type, subType); 1217 this.setHeader(ct); 1218 this.messageContent = messageContent; 1219 this.messageContentBytes = null; 1220 this.messageContentObject = null; 1221 // Could be double byte so we need to compute length 1222 // after converting to byte[] 1223 computeContentLength(messageContent); 1224 } 1225 1226 /** 1227 * Set the message content after converting the given object to a String. 1228 * 1229 * @param content -- content to set. 1230 * @param contentTypeHeader -- content type header corresponding to content. 1231 */ setContent(Object content, ContentTypeHeader contentTypeHeader)1232 public void setContent(Object content, ContentTypeHeader contentTypeHeader) 1233 throws ParseException { 1234 if (content == null) 1235 throw new NullPointerException("null content"); 1236 this.setHeader(contentTypeHeader); 1237 1238 this.messageContent = null; 1239 this.messageContentBytes = null; 1240 this.messageContentObject = null; 1241 1242 if (content instanceof String) { 1243 this.messageContent = (String) content; 1244 } else if (content instanceof byte[]) { 1245 this.messageContentBytes = (byte[]) content; 1246 } else 1247 this.messageContentObject = content; 1248 1249 computeContentLength(content); 1250 } 1251 1252 /** 1253 * Get the content (body) of the message. 1254 * 1255 * @return the content of the sip message. 1256 */ getContent()1257 public Object getContent() { 1258 if (this.messageContentObject != null) 1259 return messageContentObject; 1260 else if (this.messageContent != null) 1261 return this.messageContent; 1262 else if (this.messageContentBytes != null) 1263 return this.messageContentBytes; 1264 else 1265 return null; 1266 } 1267 1268 /** 1269 * Set the message content for a given type and subtype. 1270 * 1271 * @param type is the messge type. 1272 * @param subType is the message subType. 1273 * @param messageContent is the message content as a byte array. 1274 */ setMessageContent(String type, String subType, byte[] messageContent)1275 public void setMessageContent(String type, String subType, byte[] messageContent) { 1276 ContentType ct = new ContentType(type, subType); 1277 this.setHeader(ct); 1278 this.setMessageContent(messageContent); 1279 1280 computeContentLength(messageContent); 1281 } 1282 1283 /** 1284 * Set the message content for this message. 1285 * 1286 * @param content Message body as a string. 1287 */ setMessageContent(String content, boolean strict, boolean computeContentLength, int givenLength)1288 public void setMessageContent(String content, boolean strict, boolean computeContentLength, int givenLength) 1289 throws ParseException { 1290 // Note that that this could be a double byte character 1291 // set - bug report by Masafumi Watanabe 1292 computeContentLength(content); 1293 if ((!computeContentLength)) { 1294 if ( (!strict && this.contentLengthHeader.getContentLength() != givenLength) 1295 || this.contentLengthHeader.getContentLength() < givenLength) { 1296 throw new ParseException("Invalid content length " 1297 + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0); 1298 } 1299 } 1300 1301 messageContent = content; 1302 messageContentBytes = null; 1303 messageContentObject = null; 1304 } 1305 1306 /** 1307 * Set the message content as an array of bytes. 1308 * 1309 * @param content is the content of the message as an array of bytes. 1310 */ setMessageContent(byte[] content)1311 public void setMessageContent(byte[] content) { 1312 computeContentLength(content); 1313 1314 messageContentBytes = content; 1315 messageContent = null; 1316 messageContentObject = null; 1317 } 1318 1319 /** 1320 * Method to set the content - called by the parser 1321 * 1322 * @param content 1323 * @throws ParseException 1324 */ setMessageContent(byte[] content, boolean computeContentLength, int givenLength)1325 public void setMessageContent(byte[] content, boolean computeContentLength, int givenLength) 1326 throws ParseException { 1327 computeContentLength(content); 1328 if ((!computeContentLength) && this.contentLengthHeader.getContentLength() < givenLength) { 1329 // System.out.println("!!!!!!!!!!! MISMATCH !!!!!!!!!!!"); 1330 throw new ParseException("Invalid content length " 1331 + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0); 1332 } 1333 messageContentBytes = content; 1334 messageContent = null; 1335 messageContentObject = null; 1336 } 1337 1338 /** 1339 * Compute and set the Content-length header based on the given content object. 1340 * 1341 * @param content is the content, as String, array of bytes, or other object. 1342 */ computeContentLength(Object content)1343 private void computeContentLength(Object content) { 1344 int length = 0; 1345 if (content != null) { 1346 if (content instanceof String) { 1347 try { 1348 length = ((String) content).getBytes( getCharset() ).length; 1349 } catch (UnsupportedEncodingException ex) { 1350 InternalErrorHandler.handleException(ex); 1351 } 1352 } else if (content instanceof byte[]) { 1353 length = ((byte[]) content).length; 1354 } else { 1355 length = content.toString().length(); 1356 } 1357 } 1358 1359 try { 1360 contentLengthHeader.setContentLength(length); 1361 } catch (InvalidArgumentException e) { 1362 // Cannot happen. 1363 } 1364 } 1365 1366 /** 1367 * Remove the message content if it exists. 1368 */ removeContent()1369 public void removeContent() { 1370 messageContent = null; 1371 messageContentBytes = null; 1372 messageContentObject = null; 1373 try { 1374 this.contentLengthHeader.setContentLength(0); 1375 } catch (InvalidArgumentException ex) { 1376 } 1377 } 1378 1379 /** 1380 * Get a SIP header or Header list given its name. 1381 * 1382 * @param headerName is the name of the header to get. 1383 * @return a header or header list that contians the retrieved header. 1384 */ 1385 @SuppressWarnings("unchecked") getHeaders(String headerName)1386 public ListIterator<SIPHeader> getHeaders(String headerName) { 1387 if (headerName == null) 1388 throw new NullPointerException("null headerName"); 1389 SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache 1390 .toLowerCase(headerName)); 1391 // empty iterator 1392 if (sipHeader == null) 1393 return new LinkedList<SIPHeader>().listIterator(); 1394 if (sipHeader instanceof SIPHeaderList) { 1395 return ((SIPHeaderList<SIPHeader>) sipHeader).listIterator(); 1396 } else { 1397 return new HeaderIterator(this, sipHeader); 1398 } 1399 } 1400 1401 /** 1402 * Get a header of the given name as a string. This concatenates the headers of a given type 1403 * as a comma separted list. This is useful for formatting and printing headers. 1404 * 1405 * @param name 1406 * @return the header as a formatted string 1407 */ getHeaderAsFormattedString(String name)1408 public String getHeaderAsFormattedString(String name) { 1409 String lowerCaseName = name.toLowerCase(); 1410 if (this.nameTable.containsKey(lowerCaseName)) { 1411 return this.nameTable.get(lowerCaseName).toString(); 1412 } else { 1413 return this.getHeader(name).toString(); 1414 } 1415 } 1416 getSIPHeaderListLowerCase(String lowerCaseHeaderName)1417 private SIPHeader getSIPHeaderListLowerCase(String lowerCaseHeaderName) { 1418 return nameTable.get(lowerCaseHeaderName); 1419 } 1420 1421 /** 1422 * Get a list of headers of the given name ( or null if no such header exists ). 1423 * 1424 * @param headerName -- a header name from which to retrieve the list. 1425 * @return -- a list of headers with that name. 1426 */ 1427 @SuppressWarnings("unchecked") getHeaderList(String headerName)1428 private List<SIPHeader> getHeaderList(String headerName) { 1429 SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache 1430 .toLowerCase(headerName)); 1431 if (sipHeader == null) 1432 return null; 1433 else if (sipHeader instanceof SIPHeaderList) 1434 return (List<SIPHeader>) (((SIPHeaderList< ? >) sipHeader).getHeaderList()); 1435 else { 1436 LinkedList<SIPHeader> ll = new LinkedList<SIPHeader>(); 1437 ll.add(sipHeader); 1438 return ll; 1439 } 1440 } 1441 1442 /** 1443 * Return true if the SIPMessage has a header of the given name. 1444 * 1445 * @param headerName is the header name for which we are testing. 1446 * @return true if the header is present in the message 1447 */ hasHeader(String headerName)1448 public boolean hasHeader(String headerName) { 1449 return nameTable.containsKey(SIPHeaderNamesCache.toLowerCase(headerName)); 1450 } 1451 1452 /** 1453 * Return true if the message has a From header tag. 1454 * 1455 * @return true if the message has a from header and that header has a tag. 1456 */ hasFromTag()1457 public boolean hasFromTag() { 1458 return fromHeader != null && fromHeader.getTag() != null; 1459 } 1460 1461 /** 1462 * Return true if the message has a To header tag. 1463 * 1464 * @return true if the message has a to header and that header has a tag. 1465 */ hasToTag()1466 public boolean hasToTag() { 1467 return toHeader != null && toHeader.getTag() != null; 1468 } 1469 1470 /** 1471 * Return the from tag. 1472 * 1473 * @return the tag from the from header. 1474 * 1475 */ getFromTag()1476 public String getFromTag() { 1477 return fromHeader == null ? null : fromHeader.getTag(); 1478 } 1479 1480 /** 1481 * Set the From Tag. 1482 * 1483 * @param tag -- tag to set in the from header. 1484 */ setFromTag(String tag)1485 public void setFromTag(String tag) { 1486 try { 1487 fromHeader.setTag(tag); 1488 } catch (ParseException e) { 1489 } 1490 } 1491 1492 /** 1493 * Set the to tag. 1494 * 1495 * @param tag -- tag to set. 1496 */ setToTag(String tag)1497 public void setToTag(String tag) { 1498 try { 1499 toHeader.setTag(tag); 1500 } catch (ParseException e) { 1501 } 1502 } 1503 1504 /** 1505 * Return the to tag. 1506 */ getToTag()1507 public String getToTag() { 1508 return toHeader == null ? null : toHeader.getTag(); 1509 } 1510 1511 /** 1512 * Return the encoded first line. 1513 */ getFirstLine()1514 public abstract String getFirstLine(); 1515 1516 /** 1517 * Add a SIP header. 1518 * 1519 * @param sipHeader -- sip header to add. 1520 */ addHeader(Header sipHeader)1521 public void addHeader(Header sipHeader) { 1522 // Content length is never stored. Just computed. 1523 SIPHeader sh = (SIPHeader) sipHeader; 1524 try { 1525 if ((sipHeader instanceof ViaHeader) || (sipHeader instanceof RecordRouteHeader)) { 1526 attachHeader(sh, false, true); 1527 } else { 1528 attachHeader(sh, false, false); 1529 } 1530 } catch (SIPDuplicateHeaderException ex) { 1531 try { 1532 if (sipHeader instanceof ContentLength) { 1533 ContentLength cl = (ContentLength) sipHeader; 1534 contentLengthHeader.setContentLength(cl.getContentLength()); 1535 } 1536 } catch (InvalidArgumentException e) { 1537 } 1538 } 1539 } 1540 1541 /** 1542 * Add a header to the unparsed list of headers. 1543 * 1544 * @param unparsed -- unparsed header to add to the list. 1545 */ addUnparsed(String unparsed)1546 public void addUnparsed(String unparsed) { 1547 this.unrecognizedHeaders.add(unparsed); 1548 } 1549 1550 /** 1551 * Add a SIP header. 1552 * 1553 * @param sipHeader -- string version of SIP header to add. 1554 */ 1555 addHeader(String sipHeader)1556 public void addHeader(String sipHeader) { 1557 String hdrString = sipHeader.trim() + "\n"; 1558 try { 1559 HeaderParser parser = ParserFactory.createParser(sipHeader); 1560 SIPHeader sh = parser.parse(); 1561 this.attachHeader(sh, false); 1562 } catch (ParseException ex) { 1563 this.unrecognizedHeaders.add(hdrString); 1564 } 1565 } 1566 1567 /** 1568 * Get a list containing the unrecognized headers. 1569 * 1570 * @return a linked list containing unrecongnized headers. 1571 */ getUnrecognizedHeaders()1572 public ListIterator<String> getUnrecognizedHeaders() { 1573 return this.unrecognizedHeaders.listIterator(); 1574 } 1575 1576 /** 1577 * Get the header names. 1578 * 1579 * @return a list iterator to a list of header names. These are ordered in the same order as 1580 * are present in the message. 1581 */ getHeaderNames()1582 public ListIterator<String> getHeaderNames() { 1583 Iterator<SIPHeader> li = this.headers.iterator(); 1584 LinkedList<String> retval = new LinkedList<String>(); 1585 while (li.hasNext()) { 1586 SIPHeader sipHeader = (SIPHeader) li.next(); 1587 String name = sipHeader.getName(); 1588 retval.add(name); 1589 } 1590 return retval.listIterator(); 1591 } 1592 1593 /** 1594 * Compare for equality. 1595 * 1596 * @param other -- the other object to compare with. 1597 */ equals(Object other)1598 public boolean equals(Object other) { 1599 if (!other.getClass().equals(this.getClass())) { 1600 return false; 1601 } 1602 SIPMessage otherMessage = (SIPMessage) other; 1603 Collection<SIPHeader> values = this.nameTable.values(); 1604 Iterator<SIPHeader> it = values.iterator(); 1605 if (nameTable.size() != otherMessage.nameTable.size()) { 1606 return false; 1607 } 1608 1609 while (it.hasNext()) { 1610 SIPHeader mine = (SIPHeader) it.next(); 1611 SIPHeader his = (SIPHeader) (otherMessage.nameTable.get(SIPHeaderNamesCache 1612 .toLowerCase(mine.getName()))); 1613 if (his == null) { 1614 return false; 1615 } else if (!his.equals(mine)) { 1616 return false; 1617 } 1618 } 1619 return true; 1620 } 1621 1622 /** 1623 * get content disposition header or null if no such header exists. 1624 * 1625 * @return the contentDisposition header 1626 */ getContentDisposition()1627 public javax.sip.header.ContentDispositionHeader getContentDisposition() { 1628 return (ContentDispositionHeader) getHeaderLowerCase(CONTENT_DISPOSITION_LOWERCASE); 1629 } 1630 1631 private static final String CONTENT_DISPOSITION_LOWERCASE = SIPHeaderNamesCache 1632 .toLowerCase(ContentDispositionHeader.NAME); 1633 1634 /** 1635 * get the content encoding header. 1636 * 1637 * @return the contentEncoding header. 1638 */ getContentEncoding()1639 public javax.sip.header.ContentEncodingHeader getContentEncoding() { 1640 return (ContentEncodingHeader) getHeaderLowerCase(CONTENT_ENCODING_LOWERCASE); 1641 } 1642 1643 private static final String CONTENT_ENCODING_LOWERCASE = SIPHeaderNamesCache 1644 .toLowerCase(ContentEncodingHeader.NAME); 1645 1646 /** 1647 * Get the contentLanguage header. 1648 * 1649 * @return the content language header. 1650 */ getContentLanguage()1651 public javax.sip.header.ContentLanguageHeader getContentLanguage() { 1652 return (ContentLanguageHeader) getHeaderLowerCase(CONTENT_LANGUAGE_LOWERCASE); 1653 } 1654 1655 private static final String CONTENT_LANGUAGE_LOWERCASE = SIPHeaderNamesCache 1656 .toLowerCase(ContentLanguageHeader.NAME); 1657 1658 /** 1659 * Get the exipres header. 1660 * 1661 * @return the expires header or null if one does not exist. 1662 */ getExpires()1663 public javax.sip.header.ExpiresHeader getExpires() { 1664 return (ExpiresHeader) getHeaderLowerCase(EXPIRES_LOWERCASE); 1665 } 1666 1667 private static final String EXPIRES_LOWERCASE = SIPHeaderNamesCache 1668 .toLowerCase(ExpiresHeader.NAME); 1669 1670 /** 1671 * Set the expiresHeader 1672 * 1673 * @param expiresHeader -- the expires header to set. 1674 */ 1675 setExpires(ExpiresHeader expiresHeader)1676 public void setExpires(ExpiresHeader expiresHeader) { 1677 this.setHeader(expiresHeader); 1678 } 1679 1680 /** 1681 * Set the content disposition header. 1682 * 1683 * @param contentDispositionHeader -- content disposition header. 1684 */ 1685 setContentDisposition(ContentDispositionHeader contentDispositionHeader)1686 public void setContentDisposition(ContentDispositionHeader contentDispositionHeader) { 1687 this.setHeader(contentDispositionHeader); 1688 1689 } 1690 setContentEncoding(ContentEncodingHeader contentEncodingHeader)1691 public void setContentEncoding(ContentEncodingHeader contentEncodingHeader) { 1692 this.setHeader(contentEncodingHeader); 1693 1694 } 1695 setContentLanguage(ContentLanguageHeader contentLanguageHeader)1696 public void setContentLanguage(ContentLanguageHeader contentLanguageHeader) { 1697 this.setHeader(contentLanguageHeader); 1698 } 1699 1700 /** 1701 * Set the content length header. 1702 * 1703 * @param contentLength -- content length header. 1704 */ setContentLength(ContentLengthHeader contentLength)1705 public void setContentLength(ContentLengthHeader contentLength) { 1706 try { 1707 this.contentLengthHeader.setContentLength(contentLength.getContentLength()); 1708 } catch (InvalidArgumentException ex) { 1709 } 1710 1711 } 1712 1713 /** 1714 * Set the size of all the headers. This is for book keeping. Called by the parser. 1715 * 1716 * @param size -- size of the headers. 1717 */ setSize(int size)1718 public void setSize(int size) { 1719 this.size = size; 1720 } 1721 getSize()1722 public int getSize() { 1723 return this.size; 1724 } 1725 1726 /* 1727 * (non-Javadoc) 1728 * 1729 * @see javax.sip.message.Message#addLast(javax.sip.header.Header) 1730 */ addLast(Header header)1731 public void addLast(Header header) throws SipException, NullPointerException { 1732 if (header == null) 1733 throw new NullPointerException("null arg!"); 1734 1735 try { 1736 this.attachHeader((SIPHeader) header, false, false); 1737 } catch (SIPDuplicateHeaderException ex) { 1738 throw new SipException("Cannot add header - header already exists"); 1739 } 1740 1741 } 1742 1743 /* 1744 * (non-Javadoc) 1745 * 1746 * @see javax.sip.message.Message#addFirst(javax.sip.header.Header) 1747 */ addFirst(Header header)1748 public void addFirst(Header header) throws SipException, NullPointerException { 1749 1750 if (header == null) 1751 throw new NullPointerException("null arg!"); 1752 1753 try { 1754 this.attachHeader((SIPHeader) header, false, true); 1755 } catch (SIPDuplicateHeaderException ex) { 1756 throw new SipException("Cannot add header - header already exists"); 1757 } 1758 1759 } 1760 1761 /* 1762 * (non-Javadoc) 1763 * 1764 * @see javax.sip.message.Message#removeFirst(java.lang.String) 1765 */ removeFirst(String headerName)1766 public void removeFirst(String headerName) throws NullPointerException { 1767 if (headerName == null) 1768 throw new NullPointerException("Null argument Provided!"); 1769 this.removeHeader(headerName, true); 1770 1771 } 1772 1773 /* 1774 * (non-Javadoc) 1775 * 1776 * @see javax.sip.message.Message#removeLast(java.lang.String) 1777 */ removeLast(String headerName)1778 public void removeLast(String headerName) { 1779 if (headerName == null) 1780 throw new NullPointerException("Null argument Provided!"); 1781 this.removeHeader(headerName, false); 1782 1783 } 1784 1785 /** 1786 * Set the CSeq header. 1787 * 1788 * @param cseqHeader -- CSeq Header. 1789 */ 1790 setCSeq(CSeqHeader cseqHeader)1791 public void setCSeq(CSeqHeader cseqHeader) { 1792 this.setHeader(cseqHeader); 1793 } 1794 1795 /** 1796 * Set the application data pointer. This method is not used the stack. It is provided as a 1797 * convenient way of storing book-keeping data for applications. Note that null clears the 1798 * application data pointer (releases it). 1799 * 1800 * @param applicationData -- application data pointer to set. null clears the application data 1801 * pointer. 1802 */ setApplicationData(Object applicationData)1803 public void setApplicationData(Object applicationData) { 1804 this.applicationData = applicationData; 1805 } 1806 1807 /** 1808 * Get the application data associated with this message. 1809 * 1810 * @return stored application data. 1811 */ getApplicationData()1812 public Object getApplicationData() { 1813 return this.applicationData; 1814 } 1815 1816 /** 1817 * Get the multipart MIME content 1818 * 1819 */ getMultipartMimeContent()1820 public MultipartMimeContent getMultipartMimeContent() throws ParseException { 1821 if (this.contentLengthHeader.getContentLength() == 0) { 1822 return null; 1823 } 1824 MultipartMimeContentImpl retval = new MultipartMimeContentImpl(this 1825 .getContentTypeHeader()); 1826 byte[] rawContent = getRawContent(); 1827 try { 1828 String body = new String( rawContent, getCharset() ); 1829 retval.createContentList(body); 1830 return retval; 1831 } catch (UnsupportedEncodingException e) { 1832 InternalErrorHandler.handleException(e); 1833 return null; 1834 } 1835 } 1836 getCallIdHeader()1837 public CallIdHeader getCallIdHeader() { 1838 return this.callIdHeader; 1839 } 1840 1841 getFromHeader()1842 public FromHeader getFromHeader() { 1843 return this.fromHeader; 1844 } 1845 1846 getToHeader()1847 public ToHeader getToHeader() { 1848 return this.toHeader; 1849 } 1850 1851 getTopmostViaHeader()1852 public ViaHeader getTopmostViaHeader() { 1853 return this.getTopmostVia(); 1854 } 1855 getCSeqHeader()1856 public CSeqHeader getCSeqHeader() { 1857 return this.cSeqHeader; 1858 } 1859 1860 /** 1861 * Returns the charset to use for encoding/decoding the body of this message 1862 */ getCharset()1863 protected final String getCharset() { 1864 ContentType ct = getContentTypeHeader(); 1865 if (ct!=null) { 1866 String c = ct.getCharset(); 1867 return c!=null ? c : contentEncodingCharset; 1868 } else return contentEncodingCharset; 1869 } 1870 1871 /** 1872 * Return true if this is a null request (i.e. does not have a request line ). 1873 * 1874 * @return true if null request. 1875 */ isNullRequest()1876 public boolean isNullRequest() { 1877 return this.nullRequest; 1878 } 1879 1880 /** 1881 * Set a flag to indiate this is a special message ( encoded with CRLFCRLF ). 1882 * 1883 */ setNullRequest()1884 public void setNullRequest() { 1885 this.nullRequest = true; 1886 } 1887 1888 setSIPVersion(String sipVersion)1889 public abstract void setSIPVersion(String sipVersion) throws ParseException; 1890 getSIPVersion()1891 public abstract String getSIPVersion(); 1892 toString()1893 public abstract String toString(); 1894 1895 } 1896