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 
getReasonPhrase(int rc)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      */
setStatusCode(int statusCode)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      */
getStatusLine()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      */
getStatusCode()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      */
setReasonPhrase(String reasonPhrase)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      */
getReasonPhrase()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      */
isFinalResponse(int rc)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      */
isFinalResponse()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      */
setStatusLine(StatusLine sl)373     public void setStatusLine(StatusLine sl) {
374         statusLine = sl;
375     }
376 
377     /** Constructor.
378      */
SIPResponse()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      */
debugDump()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      */
checkHeaders()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 
encode()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 
encodeMessage()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 
getMessageAsEncodedStrings()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 
clone()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      */
equals(Object other)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      */
match(Object matchObj)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 
encodeAsBytes( String transport )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      */
getDialogId(boolean isServer)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 
getDialogId(boolean isServer, String toTag)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      */
setBranch( Via via, String method )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      */
getFirstLine()652     public String getFirstLine() {
653         if (this.statusLine == null)
654             return null;
655         else
656             return this.statusLine.encode();
657     }
658 
setSIPVersion(String sipVersion)659     public void setSIPVersion(String sipVersion) {
660         this.statusLine.setSipVersion(sipVersion);
661     }
662 
getSIPVersion()663     public String getSIPVersion() {
664         return this.statusLine.getSipVersion();
665     }
666 
toString()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      */
createRequest(SipUri requestURI, Via via, CSeq cseq, From from, To to)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