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.Utils; 33 import gov.nist.javax.sip.address.SipUri; 34 import gov.nist.javax.sip.header.CSeq; 35 import gov.nist.javax.sip.header.CallID; 36 import gov.nist.javax.sip.header.ContactList; 37 import gov.nist.javax.sip.header.ContentLength; 38 import gov.nist.javax.sip.header.ContentType; 39 import gov.nist.javax.sip.header.From; 40 import gov.nist.javax.sip.header.MaxForwards; 41 import gov.nist.javax.sip.header.ReasonList; 42 import gov.nist.javax.sip.header.RecordRouteList; 43 import gov.nist.javax.sip.header.RequireList; 44 import gov.nist.javax.sip.header.SIPHeader; 45 import gov.nist.javax.sip.header.StatusLine; 46 import gov.nist.javax.sip.header.To; 47 import gov.nist.javax.sip.header.Via; 48 import gov.nist.javax.sip.header.ViaList; 49 import gov.nist.javax.sip.header.extensions.SessionExpires; 50 51 import java.io.UnsupportedEncodingException; 52 import java.text.ParseException; 53 import java.util.Iterator; 54 import java.util.LinkedList; 55 56 import javax.sip.header.ReasonHeader; 57 import javax.sip.header.ServerHeader; 58 import javax.sip.message.Request; 59 60 61 /** 62 * SIP Response structure. 63 * 64 * @version 1.2 $Revision: 1.29 $ $Date: 2009/10/25 03:07:52 $ 65 * @since 1.1 66 * 67 * @author M. Ranganathan <br/> 68 * 69 * 70 */ 71 public final class SIPResponse 72 extends SIPMessage 73 implements javax.sip.message.Response, ResponseExt { 74 protected StatusLine statusLine; 75 76 public static String getReasonPhrase(int rc) { 77 String retval = null; 78 switch (rc) { 79 80 case TRYING : 81 retval = "Trying"; 82 break; 83 84 case RINGING : 85 retval = "Ringing"; 86 break; 87 88 case CALL_IS_BEING_FORWARDED : 89 retval = "Call is being forwarded"; 90 break; 91 92 case QUEUED : 93 retval = "Queued"; 94 break; 95 96 case SESSION_PROGRESS : 97 retval = "Session progress"; 98 break; 99 100 case OK : 101 retval = "OK"; 102 break; 103 104 case ACCEPTED : 105 retval = "Accepted"; 106 break; 107 108 case MULTIPLE_CHOICES : 109 retval = "Multiple choices"; 110 break; 111 112 case MOVED_PERMANENTLY : 113 retval = "Moved permanently"; 114 break; 115 116 case MOVED_TEMPORARILY : 117 retval = "Moved Temporarily"; 118 break; 119 120 case USE_PROXY : 121 retval = "Use proxy"; 122 break; 123 124 case ALTERNATIVE_SERVICE : 125 retval = "Alternative service"; 126 break; 127 128 case BAD_REQUEST : 129 retval = "Bad request"; 130 break; 131 132 case UNAUTHORIZED : 133 retval = "Unauthorized"; 134 break; 135 136 case PAYMENT_REQUIRED : 137 retval = "Payment required"; 138 break; 139 140 case FORBIDDEN : 141 retval = "Forbidden"; 142 break; 143 144 case NOT_FOUND : 145 retval = "Not found"; 146 break; 147 148 case METHOD_NOT_ALLOWED : 149 retval = "Method not allowed"; 150 break; 151 152 case NOT_ACCEPTABLE : 153 retval = "Not acceptable"; 154 break; 155 156 case PROXY_AUTHENTICATION_REQUIRED : 157 retval = "Proxy Authentication required"; 158 break; 159 160 case REQUEST_TIMEOUT : 161 retval = "Request timeout"; 162 break; 163 164 case GONE : 165 retval = "Gone"; 166 break; 167 168 case TEMPORARILY_UNAVAILABLE : 169 retval = "Temporarily Unavailable"; 170 break; 171 172 case REQUEST_ENTITY_TOO_LARGE : 173 retval = "Request entity too large"; 174 break; 175 176 case REQUEST_URI_TOO_LONG : 177 retval = "Request-URI too large"; 178 break; 179 180 case UNSUPPORTED_MEDIA_TYPE : 181 retval = "Unsupported media type"; 182 break; 183 184 case UNSUPPORTED_URI_SCHEME : 185 retval = "Unsupported URI Scheme"; 186 break; 187 188 case BAD_EXTENSION : 189 retval = "Bad extension"; 190 break; 191 192 case EXTENSION_REQUIRED : 193 retval = "Etension Required"; 194 break; 195 196 case INTERVAL_TOO_BRIEF : 197 retval = "Interval too brief"; 198 break; 199 200 case CALL_OR_TRANSACTION_DOES_NOT_EXIST : 201 retval = "Call leg/Transaction does not exist"; 202 break; 203 204 case LOOP_DETECTED : 205 retval = "Loop detected"; 206 break; 207 208 case TOO_MANY_HOPS : 209 retval = "Too many hops"; 210 break; 211 212 case ADDRESS_INCOMPLETE : 213 retval = "Address incomplete"; 214 break; 215 216 case AMBIGUOUS : 217 retval = "Ambiguous"; 218 break; 219 220 case BUSY_HERE : 221 retval = "Busy here"; 222 break; 223 224 case REQUEST_TERMINATED : 225 retval = "Request Terminated"; 226 break; 227 228 //Issue 168, Typo fix reported by fre on the retval 229 case NOT_ACCEPTABLE_HERE : 230 retval = "Not Acceptable here"; 231 break; 232 233 case BAD_EVENT : 234 retval = "Bad Event"; 235 break; 236 237 case REQUEST_PENDING : 238 retval = "Request Pending"; 239 break; 240 241 case SERVER_INTERNAL_ERROR : 242 retval = "Server Internal Error"; 243 break; 244 245 case UNDECIPHERABLE : 246 retval = "Undecipherable"; 247 break; 248 249 case NOT_IMPLEMENTED : 250 retval = "Not implemented"; 251 break; 252 253 case BAD_GATEWAY : 254 retval = "Bad gateway"; 255 break; 256 257 case SERVICE_UNAVAILABLE : 258 retval = "Service unavailable"; 259 break; 260 261 case SERVER_TIMEOUT : 262 retval = "Gateway timeout"; 263 break; 264 265 case VERSION_NOT_SUPPORTED : 266 retval = "SIP version not supported"; 267 break; 268 269 case MESSAGE_TOO_LARGE : 270 retval = "Message Too Large"; 271 break; 272 273 case BUSY_EVERYWHERE : 274 retval = "Busy everywhere"; 275 break; 276 277 case DECLINE : 278 retval = "Decline"; 279 break; 280 281 case DOES_NOT_EXIST_ANYWHERE : 282 retval = "Does not exist anywhere"; 283 break; 284 285 case SESSION_NOT_ACCEPTABLE : 286 retval = "Session Not acceptable"; 287 break; 288 289 case CONDITIONAL_REQUEST_FAILED: 290 retval = "Conditional request failed"; 291 break; 292 293 default : 294 retval = "Unknown Status"; 295 296 } 297 return retval; 298 299 } 300 301 /** set the status code. 302 *@param statusCode is the status code to set. 303 *@throws IlegalArgumentException if invalid status code. 304 */ 305 public void setStatusCode(int statusCode) throws ParseException { 306 307 // RFC3261 defines statuscode as 3DIGIT, 606 is the highest officially 308 // defined code but extensions may add others (in theory up to 999, 309 // but in practice up to 699 since the 6xx range is defined as 'final error') 310 if (statusCode < 100 || statusCode > 699) 311 throw new ParseException("bad status code", 0); 312 if (this.statusLine == null) 313 this.statusLine = new StatusLine(); 314 this.statusLine.setStatusCode(statusCode); 315 } 316 317 /** 318 * Get the status line of the response. 319 *@return StatusLine 320 */ 321 public StatusLine getStatusLine() { 322 return statusLine; 323 } 324 325 /** Get the staus code (conveniance function). 326 *@return the status code of the status line. 327 */ 328 public int getStatusCode() { 329 return statusLine.getStatusCode(); 330 } 331 332 /** Set the reason phrase. 333 *@param reasonPhrase the reason phrase. 334 *@throws IllegalArgumentException if null string 335 */ 336 public void setReasonPhrase(String reasonPhrase) { 337 if (reasonPhrase == null) 338 throw new IllegalArgumentException("Bad reason phrase"); 339 if (this.statusLine == null) 340 this.statusLine = new StatusLine(); 341 this.statusLine.setReasonPhrase(reasonPhrase); 342 } 343 344 /** Get the reason phrase. 345 *@return the reason phrase. 346 */ 347 public String getReasonPhrase() { 348 if (statusLine == null || statusLine.getReasonPhrase() == null) 349 return ""; 350 else 351 return statusLine.getReasonPhrase(); 352 } 353 354 /** Return true if the response is a final response. 355 *@param rc is the return code. 356 *@return true if the parameter is between the range 200 and 700. 357 */ 358 public static boolean isFinalResponse(int rc) { 359 return rc >= 200 && rc < 700; 360 } 361 362 /** Is this a final response? 363 *@return true if this is a final response. 364 */ 365 public boolean isFinalResponse() { 366 return isFinalResponse(statusLine.getStatusCode()); 367 } 368 369 /** 370 * Set the status line field. 371 *@param sl Status line to set. 372 */ 373 public void setStatusLine(StatusLine sl) { 374 statusLine = sl; 375 } 376 377 /** Constructor. 378 */ 379 public SIPResponse() { 380 super(); 381 } 382 /** 383 * Print formatting function. 384 *Indent and parenthesize for pretty printing. 385 * Note -- use the encode method for formatting the message. 386 * Hack here to XMLize. 387 * 388 *@return a string for pretty printing. 389 */ 390 public String debugDump() { 391 String superstring = super.debugDump(); 392 stringRepresentation = ""; 393 sprint(SIPResponse.class.getCanonicalName()); 394 sprint("{"); 395 if (statusLine != null) { 396 sprint(statusLine.debugDump()); 397 } 398 sprint(superstring); 399 sprint("}"); 400 return stringRepresentation; 401 } 402 403 /** 404 * Check the response structure. Must have from, to CSEQ and VIA 405 * headers. 406 */ 407 public void checkHeaders() throws ParseException { 408 if (getCSeq() == null) { 409 throw new ParseException(CSeq.NAME+ " Is missing ", 0); 410 } 411 if (getTo() == null) { 412 throw new ParseException(To.NAME+ " Is missing ", 0); 413 } 414 if (getFrom() == null) { 415 throw new ParseException(From.NAME+ " Is missing ", 0); 416 } 417 if (getViaHeaders() == null) { 418 throw new ParseException(Via.NAME+ " Is missing ", 0); 419 } 420 if (getCallId() == null) { 421 throw new ParseException(CallID.NAME + " Is missing ", 0); 422 } 423 424 425 if (getStatusCode() > 699) { 426 throw new ParseException("Unknown error code!" + getStatusCode(), 0); 427 } 428 429 } 430 431 /** 432 * Encode the SIP Request as a string. 433 *@return The string encoded canonical form of the message. 434 */ 435 436 public String encode() { 437 String retval; 438 if (statusLine != null) 439 retval = statusLine.encode() + super.encode(); 440 else 441 retval = super.encode(); 442 return retval ; 443 } 444 445 /** Encode the message except for the body. 446 * 447 *@return The string except for the body. 448 */ 449 450 public String encodeMessage() { 451 String retval; 452 if (statusLine != null) 453 retval = statusLine.encode() + super.encodeSIPHeaders(); 454 else 455 retval = super.encodeSIPHeaders(); 456 return retval ; 457 } 458 459 460 461 /** Get this message as a list of encoded strings. 462 *@return LinkedList containing encoded strings for each header in 463 * the message. 464 */ 465 466 public LinkedList getMessageAsEncodedStrings() { 467 LinkedList retval = super.getMessageAsEncodedStrings(); 468 469 if (statusLine != null) 470 retval.addFirst(statusLine.encode()); 471 return retval; 472 473 } 474 475 /** 476 * Make a clone (deep copy) of this object. 477 *@return a deep copy of this object. 478 */ 479 480 public Object clone() { 481 SIPResponse retval = (SIPResponse) super.clone(); 482 if (this.statusLine != null) 483 retval.statusLine = (StatusLine) this.statusLine.clone(); 484 return retval; 485 } 486 487 488 /** 489 * Compare for equality. 490 *@param other other object to compare with. 491 */ 492 public boolean equals(Object other) { 493 if (!this.getClass().equals(other.getClass())) 494 return false; 495 SIPResponse that = (SIPResponse) other; 496 return statusLine.equals(that.statusLine) && super.equals(other); 497 } 498 499 /** 500 * Match with a template. 501 *@param matchObj template object to match ourselves with (null 502 * in any position in the template object matches wildcard) 503 */ 504 public boolean match(Object matchObj) { 505 if (matchObj == null) 506 return true; 507 else if (!matchObj.getClass().equals(this.getClass())) { 508 return false; 509 } else if (matchObj == this) 510 return true; 511 SIPResponse that = (SIPResponse) matchObj; 512 513 StatusLine rline = that.statusLine; 514 if (this.statusLine == null && rline != null) 515 return false; 516 else if (this.statusLine == rline) 517 return super.match(matchObj); 518 else { 519 520 return statusLine.match(that.statusLine) && super.match(matchObj); 521 } 522 523 } 524 525 /** Encode this into a byte array. 526 * This is used when the body has been set as a binary array 527 * and you want to encode the body as a byte array for transmission. 528 * 529 *@return a byte array containing the SIPRequest encoded as a byte 530 * array. 531 */ 532 533 public byte[] encodeAsBytes( String transport ) { 534 byte[] slbytes = null; 535 if (statusLine != null) { 536 try { 537 slbytes = statusLine.encode().getBytes("UTF-8"); 538 } catch (UnsupportedEncodingException ex) { 539 InternalErrorHandler.handleException(ex); 540 } 541 } 542 byte[] superbytes = super.encodeAsBytes( transport ); 543 byte[] retval = new byte[slbytes.length + superbytes.length]; 544 System.arraycopy(slbytes, 0, retval, 0, slbytes.length); 545 System.arraycopy(superbytes, 0, retval, slbytes.length, 546 superbytes.length); 547 return retval; 548 } 549 550 551 552 /** Get a dialog identifier. 553 * Generates a string that can be used as a dialog identifier. 554 * 555 * @param isServer is set to true if this is the UAS 556 * and set to false if this is the UAC 557 */ 558 public String getDialogId(boolean isServer) { 559 CallID cid = (CallID) this.getCallId(); 560 From from = (From) this.getFrom(); 561 To to = (To) this.getTo(); 562 StringBuffer retval = new StringBuffer(cid.getCallId()); 563 if (!isServer) { 564 //retval.append(COLON).append(from.getUserAtHostPort()); 565 if (from.getTag() != null) { 566 retval.append(COLON); 567 retval.append(from.getTag()); 568 } 569 //retval.append(COLON).append(to.getUserAtHostPort()); 570 if (to.getTag() != null) { 571 retval.append(COLON); 572 retval.append(to.getTag()); 573 } 574 } else { 575 //retval.append(COLON).append(to.getUserAtHostPort()); 576 if (to.getTag() != null) { 577 retval.append(COLON); 578 retval.append(to.getTag()); 579 } 580 //retval.append(COLON).append(from.getUserAtHostPort()); 581 if (from.getTag() != null) { 582 retval.append(COLON); 583 retval.append(from.getTag()); 584 } 585 } 586 return retval.toString().toLowerCase(); 587 } 588 589 public String getDialogId(boolean isServer, String toTag) { 590 CallID cid = (CallID) this.getCallId(); 591 From from = (From) this.getFrom(); 592 StringBuffer retval = new StringBuffer(cid.getCallId()); 593 if (!isServer) { 594 //retval.append(COLON).append(from.getUserAtHostPort()); 595 if (from.getTag() != null) { 596 retval.append(COLON); 597 retval.append(from.getTag()); 598 } 599 //retval.append(COLON).append(to.getUserAtHostPort()); 600 if (toTag != null) { 601 retval.append(COLON); 602 retval.append(toTag); 603 } 604 } else { 605 //retval.append(COLON).append(to.getUserAtHostPort()); 606 if (toTag != null) { 607 retval.append(COLON); 608 retval.append(toTag); 609 } 610 //retval.append(COLON).append(from.getUserAtHostPort()); 611 if (from.getTag() != null) { 612 retval.append(COLON); 613 retval.append(from.getTag()); 614 } 615 } 616 return retval.toString().toLowerCase(); 617 } 618 619 /** 620 * Sets the Via branch for CANCEL or ACK requests 621 * 622 * @param via 623 * @param method 624 * @throws ParseException 625 */ 626 private final void setBranch( Via via, String method ) { 627 String branch; 628 if (method.equals( Request.ACK ) ) { 629 if (statusLine.getStatusCode() >= 300 ) { 630 branch = getTopmostVia().getBranch(); // non-2xx ACK uses same branch 631 } else { 632 branch = Utils.getInstance().generateBranchId(); // 2xx ACK gets new branch 633 } 634 } else if (method.equals( Request.CANCEL )) { 635 branch = getTopmostVia().getBranch(); // CANCEL uses same branch 636 } else return; 637 638 try { 639 via.setBranch( branch ); 640 } catch (ParseException e) { 641 e.printStackTrace(); 642 } 643 } 644 645 646 /** 647 * Get the encoded first line. 648 * 649 *@return the status line encoded. 650 * 651 */ 652 public String getFirstLine() { 653 if (this.statusLine == null) 654 return null; 655 else 656 return this.statusLine.encode(); 657 } 658 659 public void setSIPVersion(String sipVersion) { 660 this.statusLine.setSipVersion(sipVersion); 661 } 662 663 public String getSIPVersion() { 664 return this.statusLine.getSipVersion(); 665 } 666 667 public String toString() { 668 if (statusLine == null) return ""; 669 else return statusLine.encode() + super.encode(); 670 } 671 672 /** 673 * Generate a request from a response. 674 * 675 * @param requestURI -- the request URI to assign to the request. 676 * @param via -- the Via header to assign to the request 677 * @param cseq -- the CSeq header to assign to the request 678 * @param from -- the From header to assign to the request 679 * @param to -- the To header to assign to the request 680 * @return -- the newly generated sip request. 681 */ 682 public SIPRequest createRequest(SipUri requestURI, Via via, CSeq cseq, From from, To to) { 683 SIPRequest newRequest = new SIPRequest(); 684 String method = cseq.getMethod(); 685 686 newRequest.setMethod(method); 687 newRequest.setRequestURI(requestURI); 688 this.setBranch( via, method ); 689 newRequest.setHeader(via); 690 newRequest.setHeader(cseq); 691 Iterator headerIterator = getHeaders(); 692 while (headerIterator.hasNext()) { 693 SIPHeader nextHeader = (SIPHeader) headerIterator.next(); 694 // Some headers do not belong in a Request .... 695 if (SIPMessage.isResponseHeader(nextHeader) 696 || nextHeader instanceof ViaList 697 || nextHeader instanceof CSeq 698 || nextHeader instanceof ContentType 699 || nextHeader instanceof ContentLength 700 || nextHeader instanceof RecordRouteList 701 || nextHeader instanceof RequireList 702 || nextHeader instanceof ContactList // JvB: added 703 || nextHeader instanceof ContentLength 704 || nextHeader instanceof ServerHeader 705 || nextHeader instanceof ReasonHeader 706 || nextHeader instanceof SessionExpires 707 || nextHeader instanceof ReasonList) { 708 continue; 709 } 710 if (nextHeader instanceof To) 711 nextHeader = (SIPHeader) to; 712 else if (nextHeader instanceof From) 713 nextHeader = (SIPHeader) from; 714 try { 715 newRequest.attachHeader(nextHeader, false); 716 } catch (SIPDuplicateHeaderException e) { 717 //Should not happen! 718 e.printStackTrace(); 719 } 720 } 721 722 try { 723 // JvB: all requests need a Max-Forwards 724 newRequest.attachHeader( new MaxForwards(70), false); 725 } catch (Exception d) { 726 727 } 728 729 if (MessageFactoryImpl.getDefaultUserAgentHeader() != null ) { 730 newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); 731 } 732 return newRequest; 733 734 } 735 } 736