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