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.javax.sip.address.*; 32 import gov.nist.core.*; 33 34 import java.util.HashSet; 35 import java.util.Hashtable; 36 import java.util.LinkedList; 37 import java.util.Set; 38 import java.io.UnsupportedEncodingException; 39 import java.util.Iterator; 40 import javax.sip.address.URI; 41 import javax.sip.message.*; 42 43 import java.text.ParseException; 44 import javax.sip.*; 45 import javax.sip.header.*; 46 47 import gov.nist.javax.sip.header.*; 48 import gov.nist.javax.sip.stack.SIPTransactionStack; 49 50 /* 51 * Acknowledgements: Mark Bednarek made a few fixes to this code. Jeff Keyser added two methods 52 * that create responses and generate cancel requests from incoming orignial requests without the 53 * additional overhead of encoding and decoding messages. Bruno Konik noticed an extraneous 54 * newline added to the end of the buffer when encoding it. Incorporates a bug report from Andreas 55 * Bystrom. Szabo Barna noticed a contact in a cancel request - this is a pointless header for 56 * cancel. Antonis Kyardis contributed bug fixes. Jeroen van Bemmel noted that method names are 57 * case sensitive, should use equals() in getting CannonicalName 58 * 59 */ 60 61 /** 62 * The SIP Request structure. 63 * 64 * @version 1.2 $Revision: 1.52 $ $Date: 2009/12/16 14:58:40 $ 65 * @since 1.1 66 * 67 * @author M. Ranganathan <br/> 68 * 69 * 70 * 71 */ 72 73 public final class SIPRequest extends SIPMessage implements javax.sip.message.Request, RequestExt { 74 75 private static final long serialVersionUID = 3360720013577322927L; 76 77 private static final String DEFAULT_USER = "ip"; 78 79 private static final String DEFAULT_TRANSPORT = "udp"; 80 81 private transient Object transactionPointer; 82 83 private RequestLine requestLine; 84 85 private transient Object messageChannel; 86 87 88 89 private transient Object inviteTransaction; // The original invite request for a 90 // given cancel request 91 92 /** 93 * Set of target refresh methods, currently: INVITE, UPDATE, SUBSCRIBE, NOTIFY, REFER 94 * 95 * A target refresh request and its response MUST have a Contact 96 */ 97 private static final Set<String> targetRefreshMethods = new HashSet<String>(); 98 99 /* 100 * A table that maps a name string to its cannonical constant. This is used to speed up 101 * parsing of messages .equals reduces to == if we use the constant value. 102 */ 103 private static final Hashtable<String, String> nameTable = new Hashtable<String, String>(); 104 putName(String name)105 private static void putName(String name) { 106 nameTable.put(name, name); 107 } 108 109 static { 110 targetRefreshMethods.add(Request.INVITE); 111 targetRefreshMethods.add(Request.UPDATE); 112 targetRefreshMethods.add(Request.SUBSCRIBE); 113 targetRefreshMethods.add(Request.NOTIFY); 114 targetRefreshMethods.add(Request.REFER); 115 116 putName(Request.INVITE); 117 putName(Request.BYE); 118 putName(Request.CANCEL); 119 putName(Request.ACK); 120 putName(Request.PRACK); 121 putName(Request.INFO); 122 putName(Request.MESSAGE); 123 putName(Request.NOTIFY); 124 putName(Request.OPTIONS); 125 putName(Request.PRACK); 126 putName(Request.PUBLISH); 127 putName(Request.REFER); 128 putName(Request.REGISTER); 129 putName(Request.SUBSCRIBE); 130 putName(Request.UPDATE); 131 132 } 133 134 /** 135 * @return true iff the method is a target refresh 136 */ isTargetRefresh(String ucaseMethod)137 public static boolean isTargetRefresh(String ucaseMethod) { 138 return targetRefreshMethods.contains(ucaseMethod); 139 } 140 141 /** 142 * @return true iff the method is a dialog creating method 143 */ isDialogCreating(String ucaseMethod)144 public static boolean isDialogCreating(String ucaseMethod) { 145 return SIPTransactionStack.isDialogCreated(ucaseMethod); 146 } 147 148 /** 149 * Set to standard constants to speed up processing. this makes equals comparisons run much 150 * faster in the stack because then it is just identity comparision. Character by char 151 * comparison is not required. The method returns the String CONSTANT corresponding to the 152 * String name. 153 * 154 */ getCannonicalName(String method)155 public static String getCannonicalName(String method) { 156 157 if (nameTable.containsKey(method)) 158 return (String) nameTable.get(method); 159 else 160 return method; 161 } 162 163 /** 164 * Get the Request Line of the SIPRequest. 165 * 166 * @return the request line of the SIP Request. 167 */ 168 getRequestLine()169 public RequestLine getRequestLine() { 170 return requestLine; 171 } 172 173 /** 174 * Set the request line of the SIP Request. 175 * 176 * @param requestLine is the request line to set in the SIP Request. 177 */ 178 setRequestLine(RequestLine requestLine)179 public void setRequestLine(RequestLine requestLine) { 180 this.requestLine = requestLine; 181 } 182 183 /** 184 * Constructor. 185 */ SIPRequest()186 public SIPRequest() { 187 super(); 188 } 189 190 /** 191 * Convert to a formatted string for pretty printing. Note that the encode method converts 192 * this into a sip message that is suitable for transmission. Note hack here if you want to 193 * convert the nice curly brackets into some grotesque XML tag. 194 * 195 * @return a string which can be used to examine the message contents. 196 * 197 */ debugDump()198 public String debugDump() { 199 String superstring = super.debugDump(); 200 stringRepresentation = ""; 201 sprint(SIPRequest.class.getName()); 202 sprint("{"); 203 if (requestLine != null) 204 sprint(requestLine.debugDump()); 205 sprint(superstring); 206 sprint("}"); 207 return stringRepresentation; 208 } 209 210 /** 211 * Check header for constraints. (1) Invite options and bye requests can only have SIP URIs in 212 * the contact headers. (2) Request must have cseq, to and from and via headers. (3) Method in 213 * request URI must match that in CSEQ. 214 */ checkHeaders()215 public void checkHeaders() throws ParseException { 216 String prefix = "Missing a required header : "; 217 218 /* Check for required headers */ 219 220 if (getCSeq() == null) { 221 throw new ParseException(prefix + CSeqHeader.NAME, 0); 222 } 223 if (getTo() == null) { 224 throw new ParseException(prefix + ToHeader.NAME, 0); 225 } 226 227 if (this.callIdHeader == null || this.callIdHeader.getCallId() == null 228 || callIdHeader.getCallId().equals("")) { 229 throw new ParseException(prefix + CallIdHeader.NAME, 0); 230 } 231 if (getFrom() == null) { 232 throw new ParseException(prefix + FromHeader.NAME, 0); 233 } 234 if (getViaHeaders() == null) { 235 throw new ParseException(prefix + ViaHeader.NAME, 0); 236 } 237 // BEGIN android-deleted 238 /* 239 if (getMaxForwards() == null) { 240 throw new ParseException(prefix + MaxForwardsHeader.NAME, 0); 241 } 242 */ 243 // END android-deleted 244 245 if (getTopmostVia() == null) 246 throw new ParseException("No via header in request! ", 0); 247 248 if (getMethod().equals(Request.NOTIFY)) { 249 if (getHeader(SubscriptionStateHeader.NAME) == null) 250 throw new ParseException(prefix + SubscriptionStateHeader.NAME, 0); 251 252 if (getHeader(EventHeader.NAME) == null) 253 throw new ParseException(prefix + EventHeader.NAME, 0); 254 255 } else if (getMethod().equals(Request.PUBLISH)) { 256 /* 257 * For determining the type of the published event state, the EPA MUST include a 258 * single Event header field in PUBLISH requests. The value of this header field 259 * indicates the event package for which this request is publishing event state. 260 */ 261 if (getHeader(EventHeader.NAME) == null) 262 throw new ParseException(prefix + EventHeader.NAME, 0); 263 } 264 265 /* 266 * RFC 3261 8.1.1.8 The Contact header field MUST be present and contain exactly one SIP 267 * or SIPS URI in any request that can result in the establishment of a dialog. For the 268 * methods defined in this specification, that includes only the INVITE request. For these 269 * requests, the scope of the Contact is global. That is, the Contact header field value 270 * contains the URI at which the UA would like to receive requests, and this URI MUST be 271 * valid even if used in subsequent requests outside of any dialogs. 272 * 273 * If the Request-URI or top Route header field value contains a SIPS URI, the Contact 274 * header field MUST contain a SIPS URI as well. 275 */ 276 if (requestLine.getMethod().equals(Request.INVITE) 277 || requestLine.getMethod().equals(Request.SUBSCRIBE) 278 || requestLine.getMethod().equals(Request.REFER)) { 279 if (this.getContactHeader() == null) { 280 // Make sure this is not a target refresh. If this is a target 281 // refresh its ok not to have a contact header. Otherwise 282 // contact header is mandatory. 283 if (this.getToTag() == null) 284 throw new ParseException(prefix + ContactHeader.NAME, 0); 285 } 286 287 if (requestLine.getUri() instanceof SipUri) { 288 String scheme = ((SipUri) requestLine.getUri()).getScheme(); 289 if ("sips".equalsIgnoreCase(scheme)) { 290 SipUri sipUri = (SipUri) this.getContactHeader().getAddress().getURI(); 291 if (!sipUri.getScheme().equals("sips")) { 292 throw new ParseException("Scheme for contact should be sips:" + sipUri, 0); 293 } 294 } 295 } 296 } 297 298 /* 299 * Contact header is mandatory for a SIP INVITE request. 300 */ 301 if (this.getContactHeader() == null 302 && (this.getMethod().equals(Request.INVITE) 303 || this.getMethod().equals(Request.REFER) || this.getMethod().equals( 304 Request.SUBSCRIBE))) { 305 throw new ParseException("Contact Header is Mandatory for a SIP INVITE", 0); 306 } 307 308 if (requestLine != null && requestLine.getMethod() != null 309 && getCSeq().getMethod() != null 310 && requestLine.getMethod().compareTo(getCSeq().getMethod()) != 0) { 311 throw new ParseException("CSEQ method mismatch with Request-Line ", 0); 312 313 } 314 315 } 316 317 /** 318 * Set the default values in the request URI if necessary. 319 */ setDefaults()320 protected void setDefaults() { 321 // The request line may be unparseable (set to null by the 322 // exception handler. 323 if (requestLine == null) 324 return; 325 String method = requestLine.getMethod(); 326 // The requestLine may be malformed! 327 if (method == null) 328 return; 329 GenericURI u = requestLine.getUri(); 330 if (u == null) 331 return; 332 if (method.compareTo(Request.REGISTER) == 0 || method.compareTo(Request.INVITE) == 0) { 333 if (u instanceof SipUri) { 334 SipUri sipUri = (SipUri) u; 335 sipUri.setUserParam(DEFAULT_USER); 336 try { 337 sipUri.setTransportParam(DEFAULT_TRANSPORT); 338 } catch (ParseException ex) { 339 } 340 } 341 } 342 } 343 344 /** 345 * Patch up the request line as necessary. 346 */ setRequestLineDefaults()347 protected void setRequestLineDefaults() { 348 String method = requestLine.getMethod(); 349 if (method == null) { 350 CSeq cseq = (CSeq) this.getCSeq(); 351 if (cseq != null) { 352 method = getCannonicalName(cseq.getMethod()); 353 requestLine.setMethod(method); 354 } 355 } 356 } 357 358 /** 359 * A conveniance function to access the Request URI. 360 * 361 * @return the requestURI if it exists. 362 */ getRequestURI()363 public javax.sip.address.URI getRequestURI() { 364 if (this.requestLine == null) 365 return null; 366 else 367 return (javax.sip.address.URI) this.requestLine.getUri(); 368 } 369 370 /** 371 * Sets the RequestURI of Request. The Request-URI is a SIP or SIPS URI or a general URI. It 372 * indicates the user or service to which this request is being addressed. SIP elements MAY 373 * support Request-URIs with schemes other than "sip" and "sips", for example the "tel" URI 374 * scheme. SIP elements MAY translate non-SIP URIs using any mechanism at their disposal, 375 * resulting in SIP URI, SIPS URI, or some other scheme. 376 * 377 * @param uri the new Request URI of this request message 378 */ setRequestURI(URI uri)379 public void setRequestURI(URI uri) { 380 if ( uri == null ) { 381 throw new NullPointerException("Null request URI"); 382 } 383 if (this.requestLine == null) { 384 this.requestLine = new RequestLine(); 385 } 386 this.requestLine.setUri((GenericURI) uri); 387 this.nullRequest = false; 388 } 389 390 /** 391 * Set the method. 392 * 393 * @param method is the method to set. 394 * @throws IllegalArgumentException if the method is null 395 */ setMethod(String method)396 public void setMethod(String method) { 397 if (method == null) 398 throw new IllegalArgumentException("null method"); 399 if (this.requestLine == null) { 400 this.requestLine = new RequestLine(); 401 } 402 403 // Set to standard constants to speed up processing. 404 // this makes equals compares run much faster in the 405 // stack because then it is just identity comparision 406 407 String meth = getCannonicalName(method); 408 this.requestLine.setMethod(meth); 409 410 if (this.cSeqHeader != null) { 411 try { 412 this.cSeqHeader.setMethod(meth); 413 } catch (ParseException e) { 414 } 415 } 416 } 417 418 /** 419 * Get the method from the request line. 420 * 421 * @return the method from the request line if the method exits and null if the request line 422 * or the method does not exist. 423 */ getMethod()424 public String getMethod() { 425 if (requestLine == null) 426 return null; 427 else 428 return requestLine.getMethod(); 429 } 430 431 /** 432 * Encode the SIP Request as a string. 433 * 434 * @return an encoded String containing the encoded SIP Message. 435 */ 436 encode()437 public String encode() { 438 String retval; 439 if (requestLine != null) { 440 this.setRequestLineDefaults(); 441 retval = requestLine.encode() + super.encode(); 442 } else if (this.isNullRequest()) { 443 retval = "\r\n\r\n"; 444 } else { 445 retval = super.encode(); 446 } 447 return retval; 448 } 449 450 /** 451 * Encode only the headers and not the content. 452 */ encodeMessage()453 public String encodeMessage() { 454 String retval; 455 if (requestLine != null) { 456 this.setRequestLineDefaults(); 457 retval = requestLine.encode() + super.encodeSIPHeaders(); 458 } else if (this.isNullRequest()) { 459 retval = "\r\n\r\n"; 460 } else 461 retval = super.encodeSIPHeaders(); 462 return retval; 463 464 } 465 466 /** 467 * ALias for encode above. 468 */ toString()469 public String toString() { 470 return this.encode(); 471 } 472 473 /** 474 * Make a clone (deep copy) of this object. You can use this if you want to modify a request 475 * while preserving the original 476 * 477 * @return a deep copy of this object. 478 */ 479 clone()480 public Object clone() { 481 SIPRequest retval = (SIPRequest) super.clone(); 482 // Do not copy over the tx pointer -- this is only for internal 483 // tracking. 484 retval.transactionPointer = null; 485 if (this.requestLine != null) 486 retval.requestLine = (RequestLine) this.requestLine.clone(); 487 488 return retval; 489 } 490 491 /** 492 * Compare for equality. 493 * 494 * @param other object to compare ourselves with. 495 */ equals(Object other)496 public boolean equals(Object other) { 497 if (!this.getClass().equals(other.getClass())) 498 return false; 499 SIPRequest that = (SIPRequest) other; 500 501 return requestLine.equals(that.requestLine) && super.equals(other); 502 } 503 504 /** 505 * Get the message as a linked list of strings. Use this if you want to iterate through the 506 * message. 507 * 508 * @return a linked list containing the request line and headers encoded as strings. 509 */ getMessageAsEncodedStrings()510 public LinkedList getMessageAsEncodedStrings() { 511 LinkedList retval = super.getMessageAsEncodedStrings(); 512 if (requestLine != null) { 513 this.setRequestLineDefaults(); 514 retval.addFirst(requestLine.encode()); 515 } 516 return retval; 517 518 } 519 520 /** 521 * Match with a template. You can use this if you want to match incoming messages with a 522 * pattern and do something when you find a match. This is useful for building filters/pattern 523 * matching responders etc. 524 * 525 * @param matchObj object to match ourselves with (null matches wildcard) 526 * 527 */ match(Object matchObj)528 public boolean match(Object matchObj) { 529 if (matchObj == null) 530 return true; 531 else if (!matchObj.getClass().equals(this.getClass())) 532 return false; 533 else if (matchObj == this) 534 return true; 535 SIPRequest that = (SIPRequest) matchObj; 536 RequestLine rline = that.requestLine; 537 if (this.requestLine == null && rline != null) 538 return false; 539 else if (this.requestLine == rline) 540 return super.match(matchObj); 541 return requestLine.match(that.requestLine) && super.match(matchObj); 542 543 } 544 545 /** 546 * Get a dialog identifier. Generates a string that can be used as a dialog identifier. 547 * 548 * @param isServer is set to true if this is the UAS and set to false if this is the UAC 549 */ getDialogId(boolean isServer)550 public String getDialogId(boolean isServer) { 551 CallID cid = (CallID) this.getCallId(); 552 StringBuffer retval = new StringBuffer(cid.getCallId()); 553 From from = (From) this.getFrom(); 554 To to = (To) this.getTo(); 555 if (!isServer) { 556 // retval.append(COLON).append(from.getUserAtHostPort()); 557 if (from.getTag() != null) { 558 retval.append(COLON); 559 retval.append(from.getTag()); 560 } 561 // retval.append(COLON).append(to.getUserAtHostPort()); 562 if (to.getTag() != null) { 563 retval.append(COLON); 564 retval.append(to.getTag()); 565 } 566 } else { 567 // retval.append(COLON).append(to.getUserAtHostPort()); 568 if (to.getTag() != null) { 569 retval.append(COLON); 570 retval.append(to.getTag()); 571 } 572 // retval.append(COLON).append(from.getUserAtHostPort()); 573 if (from.getTag() != null) { 574 retval.append(COLON); 575 retval.append(from.getTag()); 576 } 577 } 578 return retval.toString().toLowerCase(); 579 580 } 581 582 /** 583 * Get a dialog id given the remote tag. 584 */ getDialogId(boolean isServer, String toTag)585 public String getDialogId(boolean isServer, String toTag) { 586 From from = (From) this.getFrom(); 587 CallID cid = (CallID) this.getCallId(); 588 StringBuffer retval = new StringBuffer(cid.getCallId()); 589 if (!isServer) { 590 // retval.append(COLON).append(from.getUserAtHostPort()); 591 if (from.getTag() != null) { 592 retval.append(COLON); 593 retval.append(from.getTag()); 594 } 595 // retval.append(COLON).append(to.getUserAtHostPort()); 596 if (toTag != null) { 597 retval.append(COLON); 598 retval.append(toTag); 599 } 600 } else { 601 // retval.append(COLON).append(to.getUserAtHostPort()); 602 if (toTag != null) { 603 retval.append(COLON); 604 retval.append(toTag); 605 } 606 // retval.append(COLON).append(from.getUserAtHostPort()); 607 if (from.getTag() != null) { 608 retval.append(COLON); 609 retval.append(from.getTag()); 610 } 611 } 612 return retval.toString().toLowerCase(); 613 } 614 615 /** 616 * Encode this into a byte array. This is used when the body has been set as a binary array 617 * and you want to encode the body as a byte array for transmission. 618 * 619 * @return a byte array containing the SIPRequest encoded as a byte array. 620 */ 621 encodeAsBytes(String transport)622 public byte[] encodeAsBytes(String transport) { 623 if (this.isNullRequest()) { 624 // Encoding a null message for keepalive. 625 return "\r\n\r\n".getBytes(); 626 } else if ( this.requestLine == null ) { 627 return new byte[0]; 628 } 629 630 byte[] rlbytes = null; 631 if (requestLine != null) { 632 try { 633 rlbytes = requestLine.encode().getBytes("UTF-8"); 634 } catch (UnsupportedEncodingException ex) { 635 InternalErrorHandler.handleException(ex); 636 } 637 } 638 byte[] superbytes = super.encodeAsBytes(transport); 639 byte[] retval = new byte[rlbytes.length + superbytes.length]; 640 System.arraycopy(rlbytes, 0, retval, 0, rlbytes.length); 641 System.arraycopy(superbytes, 0, retval, rlbytes.length, superbytes.length); 642 return retval; 643 } 644 645 /** 646 * Creates a default SIPResponse message for this request. Note You must add the necessary 647 * tags to outgoing responses if need be. For efficiency, this method does not clone the 648 * incoming request. If you want to modify the outgoing response, be sure to clone the 649 * incoming request as the headers are shared and any modification to the headers of the 650 * outgoing response will result in a modification of the incoming request. Tag fields are 651 * just copied from the incoming request. Contact headers are removed from the incoming 652 * request. Added by Jeff Keyser. 653 * 654 * @param statusCode Status code for the response. Reason phrase is generated. 655 * 656 * @return A SIPResponse with the status and reason supplied, and a copy of all the original 657 * headers from this request. 658 */ 659 createResponse(int statusCode)660 public SIPResponse createResponse(int statusCode) { 661 662 String reasonPhrase = SIPResponse.getReasonPhrase(statusCode); 663 return this.createResponse(statusCode, reasonPhrase); 664 665 } 666 667 /** 668 * Creates a default SIPResponse message for this request. Note You must add the necessary 669 * tags to outgoing responses if need be. For efficiency, this method does not clone the 670 * incoming request. If you want to modify the outgoing response, be sure to clone the 671 * incoming request as the headers are shared and any modification to the headers of the 672 * outgoing response will result in a modification of the incoming request. Tag fields are 673 * just copied from the incoming request. Contact headers are removed from the incoming 674 * request. Added by Jeff Keyser. Route headers are not added to the response. 675 * 676 * @param statusCode Status code for the response. 677 * @param reasonPhrase Reason phrase for this response. 678 * 679 * @return A SIPResponse with the status and reason supplied, and a copy of all the original 680 * headers from this request except the ones that are not supposed to be part of the 681 * response . 682 */ 683 createResponse(int statusCode, String reasonPhrase)684 public SIPResponse createResponse(int statusCode, String reasonPhrase) { 685 SIPResponse newResponse; 686 Iterator headerIterator; 687 SIPHeader nextHeader; 688 689 newResponse = new SIPResponse(); 690 try { 691 newResponse.setStatusCode(statusCode); 692 } catch (ParseException ex) { 693 throw new IllegalArgumentException("Bad code " + statusCode); 694 } 695 if (reasonPhrase != null) 696 newResponse.setReasonPhrase(reasonPhrase); 697 else 698 newResponse.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode)); 699 headerIterator = getHeaders(); 700 while (headerIterator.hasNext()) { 701 nextHeader = (SIPHeader) headerIterator.next(); 702 if (nextHeader instanceof From 703 || nextHeader instanceof To 704 || nextHeader instanceof ViaList 705 || nextHeader instanceof CallID 706 || (nextHeader instanceof RecordRouteList && mustCopyRR(statusCode)) 707 || nextHeader instanceof CSeq 708 // We just copy TimeStamp for all headers (not just 100). 709 || nextHeader instanceof TimeStamp) { 710 711 try { 712 713 newResponse.attachHeader((SIPHeader) nextHeader.clone(), false); 714 } catch (SIPDuplicateHeaderException e) { 715 e.printStackTrace(); 716 } 717 } 718 } 719 if (MessageFactoryImpl.getDefaultServerHeader() != null) { 720 newResponse.setHeader(MessageFactoryImpl.getDefaultServerHeader()); 721 722 } 723 if (newResponse.getStatusCode() == 100) { 724 // Trying is never supposed to have the tag parameter set. 725 newResponse.getTo().removeParameter("tag"); 726 727 } 728 ServerHeader server = MessageFactoryImpl.getDefaultServerHeader(); 729 if (server != null) { 730 newResponse.setHeader(server); 731 } 732 return newResponse; 733 } 734 735 // Helper method for createResponse, to avoid copying Record-Route unless needed mustCopyRR( int code )736 private final boolean mustCopyRR( int code ) { 737 // Only for 1xx-2xx, not for 100 or errors 738 if ( code>100 && code<300 ) { 739 return isDialogCreating( this.getMethod() ) && getToTag() == null; 740 } else return false; 741 } 742 743 /** 744 * Creates a default SIPResquest message that would cancel this request. Note that tag 745 * assignment and removal of is left to the caller (we use whatever tags are present in the 746 * original request). 747 * 748 * @return A CANCEL SIPRequest constructed according to RFC3261 section 9.1 749 * 750 * @throws SipException 751 * @throws ParseException 752 */ createCancelRequest()753 public SIPRequest createCancelRequest() throws SipException { 754 755 // see RFC3261 9.1 756 757 // A CANCEL request SHOULD NOT be sent to cancel a request other than 758 // INVITE 759 760 if (!this.getMethod().equals(Request.INVITE)) 761 throw new SipException("Attempt to create CANCEL for " + this.getMethod()); 762 763 /* 764 * The following procedures are used to construct a CANCEL request. The Request-URI, 765 * Call-ID, To, the numeric part of CSeq, and From header fields in the CANCEL request 766 * MUST be identical to those in the request being cancelled, including tags. A CANCEL 767 * constructed by a client MUST have only a single Via header field value matching the top 768 * Via value in the request being cancelled. Using the same values for these header fields 769 * allows the CANCEL to be matched with the request it cancels (Section 9.2 indicates how 770 * such matching occurs). However, the method part of the CSeq header field MUST have a 771 * value of CANCEL. This allows it to be identified and processed as a transaction in its 772 * own right (See Section 17). 773 */ 774 SIPRequest cancel = new SIPRequest(); 775 cancel.setRequestLine((RequestLine) this.requestLine.clone()); 776 cancel.setMethod(Request.CANCEL); 777 cancel.setHeader((Header) this.callIdHeader.clone()); 778 cancel.setHeader((Header) this.toHeader.clone()); 779 cancel.setHeader((Header) cSeqHeader.clone()); 780 try { 781 cancel.getCSeq().setMethod(Request.CANCEL); 782 } catch (ParseException e) { 783 e.printStackTrace(); // should not happen 784 } 785 cancel.setHeader((Header) this.fromHeader.clone()); 786 787 cancel.addFirst((Header) this.getTopmostVia().clone()); 788 cancel.setHeader((Header) this.maxForwardsHeader.clone()); 789 790 /* 791 * If the request being cancelled contains a Route header field, the CANCEL request MUST 792 * include that Route header field's values. 793 */ 794 if (this.getRouteHeaders() != null) { 795 cancel.setHeader((SIPHeaderList< ? >) this.getRouteHeaders().clone()); 796 } 797 if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { 798 cancel.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); 799 800 } 801 return cancel; 802 } 803 804 /** 805 * Creates a default ACK SIPRequest message for this original request. Note that the 806 * defaultACK SIPRequest does not include the content of the original SIPRequest. If 807 * responseToHeader is null then the toHeader of this request is used to construct the ACK. 808 * Note that tag fields are just copied from the original SIP Request. Added by Jeff Keyser. 809 * 810 * @param responseToHeader To header to use for this request. 811 * 812 * @return A SIPRequest with an ACK method. 813 */ createAckRequest(To responseToHeader)814 public SIPRequest createAckRequest(To responseToHeader) { 815 SIPRequest newRequest; 816 Iterator headerIterator; 817 SIPHeader nextHeader; 818 819 newRequest = new SIPRequest(); 820 newRequest.setRequestLine((RequestLine) this.requestLine.clone()); 821 newRequest.setMethod(Request.ACK); 822 headerIterator = getHeaders(); 823 while (headerIterator.hasNext()) { 824 nextHeader = (SIPHeader) headerIterator.next(); 825 if (nextHeader instanceof RouteList) { 826 // Ack and cancel do not get ROUTE headers. 827 // Route header for ACK is assigned by the 828 // Dialog if necessary. 829 continue; 830 } else if (nextHeader instanceof ProxyAuthorization) { 831 // Remove proxy auth header. 832 // Assigned by the Dialog if necessary. 833 continue; 834 } else if (nextHeader instanceof ContentLength) { 835 // Adding content is responsibility of user. 836 nextHeader = (SIPHeader) nextHeader.clone(); 837 try { 838 ((ContentLength) nextHeader).setContentLength(0); 839 } catch (InvalidArgumentException e) { 840 } 841 } else if (nextHeader instanceof ContentType) { 842 // Content type header is removed since 843 // content length is 0. 844 continue; 845 } else if (nextHeader instanceof CSeq) { 846 // The CSeq header field in the 847 // ACK MUST contain the same value for the 848 // sequence number as was present in the 849 // original request, but the method parameter 850 // MUST be equal to "ACK". 851 CSeq cseq = (CSeq) nextHeader.clone(); 852 try { 853 cseq.setMethod(Request.ACK); 854 } catch (ParseException e) { 855 } 856 nextHeader = cseq; 857 } else if (nextHeader instanceof To) { 858 if (responseToHeader != null) { 859 nextHeader = responseToHeader; 860 } else { 861 nextHeader = (SIPHeader) nextHeader.clone(); 862 } 863 } else if (nextHeader instanceof ContactList || nextHeader instanceof Expires) { 864 // CONTACT header does not apply for ACK requests. 865 continue; 866 } else if (nextHeader instanceof ViaList) { 867 // Bug reported by Gianluca Martinello 868 // The ACK MUST contain a single Via header field, 869 // and this MUST be equal to the top Via header 870 // field of the original 871 // request. 872 873 nextHeader = (SIPHeader) ((ViaList) nextHeader).getFirst().clone(); 874 } else { 875 nextHeader = (SIPHeader) nextHeader.clone(); 876 } 877 878 try { 879 newRequest.attachHeader(nextHeader, false); 880 } catch (SIPDuplicateHeaderException e) { 881 e.printStackTrace(); 882 } 883 } 884 if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { 885 newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); 886 887 } 888 return newRequest; 889 } 890 891 /** 892 * Creates an ACK for non-2xx responses according to RFC3261 17.1.1.3 893 * 894 * @return A SIPRequest with an ACK method. 895 * @throws SipException 896 * @throws NullPointerException 897 * @throws ParseException 898 * 899 * @author jvb 900 */ createErrorAck(To responseToHeader)901 public final SIPRequest createErrorAck(To responseToHeader) throws SipException, 902 ParseException { 903 904 /* 905 * The ACK request constructed by the client transaction MUST contain values for the 906 * Call-ID, From, and Request-URI that are equal to the values of those header fields in 907 * the request passed to the transport by the client transaction (call this the "original 908 * request"). The To header field in the ACK MUST equal the To header field in the 909 * response being acknowledged, and therefore will usually differ from the To header field 910 * in the original request by the addition of the tag parameter. The ACK MUST contain a 911 * single Via header field, and this MUST be equal to the top Via header field of the 912 * original request. The CSeq header field in the ACK MUST contain the same value for the 913 * sequence number as was present in the original request, but the method parameter MUST 914 * be equal to "ACK". 915 */ 916 SIPRequest newRequest = new SIPRequest(); 917 newRequest.setRequestLine((RequestLine) this.requestLine.clone()); 918 newRequest.setMethod(Request.ACK); 919 newRequest.setHeader((Header) this.callIdHeader.clone()); 920 newRequest.setHeader((Header) this.maxForwardsHeader.clone()); // ISSUE 921 // 130 922 // fix 923 newRequest.setHeader((Header) this.fromHeader.clone()); 924 newRequest.setHeader((Header) responseToHeader.clone()); 925 newRequest.addFirst((Header) this.getTopmostVia().clone()); 926 newRequest.setHeader((Header) cSeqHeader.clone()); 927 newRequest.getCSeq().setMethod(Request.ACK); 928 929 /* 930 * If the INVITE request whose response is being acknowledged had Route header fields, 931 * those header fields MUST appear in the ACK. This is to ensure that the ACK can be 932 * routed properly through any downstream stateless proxies. 933 */ 934 if (this.getRouteHeaders() != null) { 935 newRequest.setHeader((SIPHeaderList) this.getRouteHeaders().clone()); 936 } 937 if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { 938 newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); 939 940 } 941 return newRequest; 942 } 943 944 /** 945 * Create a new default SIPRequest from the original request. Warning: the newly created 946 * SIPRequest, shares the headers of this request but we generate any new headers that we need 947 * to modify so the original request is umodified. However, if you modify the shared headers 948 * after this request is created, then the newly created request will also be modified. If you 949 * want to modify the original request without affecting the returned Request make sure you 950 * clone it before calling this method. 951 * 952 * Only required headers are copied. 953 * <ul> 954 * <li> Contact headers are not included in the newly created request. Setting the appropriate 955 * sequence number is the responsibility of the caller. </li> 956 * <li> RouteList is not copied for ACK and CANCEL </li> 957 * <li> Note that we DO NOT copy the body of the argument into the returned header. We do not 958 * copy the content type header from the original request either. These have to be added 959 * seperately and the content length has to be correctly set if necessary the content length 960 * is set to 0 in the returned header. </li> 961 * <li>Contact List is not copied from the original request.</li> 962 * <li>RecordRoute List is not included from original request. </li> 963 * <li>Via header is not included from the original request. </li> 964 * </ul> 965 * 966 * @param requestLine is the new request line. 967 * 968 * @param switchHeaders is a boolean flag that causes to and from headers to switch (set this 969 * to true if you are the server of the transaction and are generating a BYE request). 970 * If the headers are switched, we generate new From and To headers otherwise we just 971 * use the incoming headers. 972 * 973 * @return a new Default SIP Request which has the requestLine specified. 974 * 975 */ createSIPRequest(RequestLine requestLine, boolean switchHeaders)976 public SIPRequest createSIPRequest(RequestLine requestLine, boolean switchHeaders) { 977 SIPRequest newRequest = new SIPRequest(); 978 newRequest.requestLine = requestLine; 979 Iterator<SIPHeader> headerIterator = this.getHeaders(); 980 while (headerIterator.hasNext()) { 981 SIPHeader nextHeader = (SIPHeader) headerIterator.next(); 982 // For BYE and cancel set the CSeq header to the 983 // appropriate method. 984 if (nextHeader instanceof CSeq) { 985 CSeq newCseq = (CSeq) nextHeader.clone(); 986 nextHeader = newCseq; 987 try { 988 newCseq.setMethod(requestLine.getMethod()); 989 } catch (ParseException e) { 990 } 991 } else if (nextHeader instanceof ViaList) { 992 Via via = (Via) (((ViaList) nextHeader).getFirst().clone()); 993 via.removeParameter("branch"); 994 nextHeader = via; 995 // Cancel and ACK preserve the branch ID. 996 } else if (nextHeader instanceof To) { 997 To to = (To) nextHeader; 998 if (switchHeaders) { 999 nextHeader = new From(to); 1000 ((From) nextHeader).removeTag(); 1001 } else { 1002 nextHeader = (SIPHeader) to.clone(); 1003 ((To) nextHeader).removeTag(); 1004 } 1005 } else if (nextHeader instanceof From) { 1006 From from = (From) nextHeader; 1007 if (switchHeaders) { 1008 nextHeader = new To(from); 1009 ((To) nextHeader).removeTag(); 1010 } else { 1011 nextHeader = (SIPHeader) from.clone(); 1012 ((From) nextHeader).removeTag(); 1013 } 1014 } else if (nextHeader instanceof ContentLength) { 1015 ContentLength cl = (ContentLength) nextHeader.clone(); 1016 try { 1017 cl.setContentLength(0); 1018 } catch (InvalidArgumentException e) { 1019 } 1020 nextHeader = cl; 1021 } else if (!(nextHeader instanceof CallID) && !(nextHeader instanceof MaxForwards)) { 1022 // Route is kept by dialog. 1023 // RR is added by the caller. 1024 // Contact is added by the Caller 1025 // Any extension headers must be added 1026 // by the caller. 1027 continue; 1028 } 1029 try { 1030 newRequest.attachHeader(nextHeader, false); 1031 } catch (SIPDuplicateHeaderException e) { 1032 e.printStackTrace(); 1033 } 1034 } 1035 if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { 1036 newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); 1037 1038 } 1039 return newRequest; 1040 1041 } 1042 1043 /** 1044 * Create a BYE request from this request. 1045 * 1046 * @param switchHeaders is a boolean flag that causes from and isServerTransaction to headers 1047 * to be swapped. Set this to true if you are the server of the dialog and are 1048 * generating a BYE request for the dialog. 1049 * @return a new default BYE request. 1050 */ createBYERequest(boolean switchHeaders)1051 public SIPRequest createBYERequest(boolean switchHeaders) { 1052 RequestLine requestLine = (RequestLine) this.requestLine.clone(); 1053 requestLine.setMethod("BYE"); 1054 return this.createSIPRequest(requestLine, switchHeaders); 1055 } 1056 1057 /** 1058 * Create an ACK request from this request. This is suitable for generating an ACK for an 1059 * INVITE client transaction. 1060 * 1061 * @return an ACK request that is generated from this request. 1062 */ createACKRequest()1063 public SIPRequest createACKRequest() { 1064 RequestLine requestLine = (RequestLine) this.requestLine.clone(); 1065 requestLine.setMethod(Request.ACK); 1066 return this.createSIPRequest(requestLine, false); 1067 } 1068 1069 /** 1070 * Get the host from the topmost via header. 1071 * 1072 * @return the string representation of the host from the topmost via header. 1073 */ getViaHost()1074 public String getViaHost() { 1075 Via via = (Via) this.getViaHeaders().getFirst(); 1076 return via.getHost(); 1077 1078 } 1079 1080 /** 1081 * Get the port from the topmost via header. 1082 * 1083 * @return the port from the topmost via header (5060 if there is no port indicated). 1084 */ getViaPort()1085 public int getViaPort() { 1086 Via via = (Via) this.getViaHeaders().getFirst(); 1087 if (via.hasPort()) 1088 return via.getPort(); 1089 else 1090 return 5060; 1091 } 1092 1093 /** 1094 * Get the first line encoded. 1095 * 1096 * @return a string containing the encoded request line. 1097 */ getFirstLine()1098 public String getFirstLine() { 1099 if (requestLine == null) 1100 return null; 1101 else 1102 return this.requestLine.encode(); 1103 } 1104 1105 /** 1106 * Set the sip version. 1107 * 1108 * @param sipVersion the sip version to set. 1109 */ setSIPVersion(String sipVersion)1110 public void setSIPVersion(String sipVersion) throws ParseException { 1111 if (sipVersion == null || !sipVersion.equalsIgnoreCase("SIP/2.0")) 1112 throw new ParseException("sipVersion", 0); 1113 this.requestLine.setSipVersion(sipVersion); 1114 } 1115 1116 /** 1117 * Get the SIP version. 1118 * 1119 * @return the SIP version from the request line. 1120 */ getSIPVersion()1121 public String getSIPVersion() { 1122 return this.requestLine.getSipVersion(); 1123 } 1124 1125 /** 1126 * Book keeping method to return the current tx for the request if one exists. 1127 * 1128 * @return the assigned tx. 1129 */ getTransaction()1130 public Object getTransaction() { 1131 // Return an opaque pointer to the transaction object. 1132 // This is for consistency checking and quick lookup. 1133 return this.transactionPointer; 1134 } 1135 1136 /** 1137 * Book keeping field to set the current tx for the request. 1138 * 1139 * @param transaction 1140 */ setTransaction(Object transaction)1141 public void setTransaction(Object transaction) { 1142 this.transactionPointer = transaction; 1143 } 1144 1145 /** 1146 * Book keeping method to get the messasge channel for the request. 1147 * 1148 * @return the message channel for the request. 1149 */ 1150 getMessageChannel()1151 public Object getMessageChannel() { 1152 // return opaque ptr to the message chanel on 1153 // which the message was recieved. For consistency 1154 // checking and lookup. 1155 return this.messageChannel; 1156 } 1157 1158 /** 1159 * Set the message channel for the request ( bookkeeping field ). 1160 * 1161 * @param messageChannel 1162 */ 1163 setMessageChannel(Object messageChannel)1164 public void setMessageChannel(Object messageChannel) { 1165 this.messageChannel = messageChannel; 1166 } 1167 1168 /** 1169 * Generates an Id for checking potentially merged requests. 1170 * 1171 * @return String to check for merged requests 1172 */ getMergeId()1173 public String getMergeId() { 1174 /* 1175 * generate an identifier from the From tag, Call-ID, and CSeq 1176 */ 1177 String fromTag = this.getFromTag(); 1178 String cseq = this.cSeqHeader.toString(); 1179 String callId = this.callIdHeader.getCallId(); 1180 /* NOTE : The RFC does NOT specify you need to include a Request URI 1181 * This is added here for the case of Back to Back User Agents. 1182 */ 1183 String requestUri = this.getRequestURI().toString(); 1184 1185 if (fromTag != null) { 1186 return new StringBuffer().append(requestUri).append(":").append(fromTag).append(":").append(cseq).append(":") 1187 .append(callId).toString(); 1188 } else 1189 return null; 1190 1191 } 1192 1193 /** 1194 * @param inviteTransaction the inviteTransaction to set 1195 */ setInviteTransaction(Object inviteTransaction)1196 public void setInviteTransaction(Object inviteTransaction) { 1197 this.inviteTransaction = inviteTransaction; 1198 } 1199 1200 /** 1201 * @return the inviteTransaction 1202 */ getInviteTransaction()1203 public Object getInviteTransaction() { 1204 return inviteTransaction; 1205 } 1206 1207 1208 1209 1210 1211 } 1212