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 *******************************************************************************/
29package gov.nist.javax.sip.message;
30
31import gov.nist.core.InternalErrorHandler;
32import gov.nist.javax.sip.Utils;
33import gov.nist.javax.sip.address.SipUri;
34import gov.nist.javax.sip.header.CSeq;
35import gov.nist.javax.sip.header.CallID;
36import gov.nist.javax.sip.header.ContactList;
37import gov.nist.javax.sip.header.ContentLength;
38import gov.nist.javax.sip.header.ContentType;
39import gov.nist.javax.sip.header.From;
40import gov.nist.javax.sip.header.MaxForwards;
41import gov.nist.javax.sip.header.ReasonList;
42import gov.nist.javax.sip.header.RecordRouteList;
43import gov.nist.javax.sip.header.RequireList;
44import gov.nist.javax.sip.header.SIPHeader;
45import gov.nist.javax.sip.header.StatusLine;
46import gov.nist.javax.sip.header.To;
47import gov.nist.javax.sip.header.Via;
48import gov.nist.javax.sip.header.ViaList;
49import gov.nist.javax.sip.header.extensions.SessionExpires;
50
51import java.io.UnsupportedEncodingException;
52import java.text.ParseException;
53import java.util.Iterator;
54import java.util.LinkedList;
55
56import javax.sip.header.ReasonHeader;
57import javax.sip.header.ServerHeader;
58import 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 */
71public 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