Home | History | Annotate | Download | only in message
      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.SIPConstants;
     33 import gov.nist.javax.sip.Utils;
     34 import gov.nist.javax.sip.header.AlertInfo;
     35 import gov.nist.javax.sip.header.Authorization;
     36 import gov.nist.javax.sip.header.CSeq;
     37 import gov.nist.javax.sip.header.CallID;
     38 import gov.nist.javax.sip.header.Contact;
     39 import gov.nist.javax.sip.header.ContactList;
     40 import gov.nist.javax.sip.header.ContentLength;
     41 import gov.nist.javax.sip.header.ContentType;
     42 import gov.nist.javax.sip.header.ErrorInfo;
     43 import gov.nist.javax.sip.header.ErrorInfoList;
     44 import gov.nist.javax.sip.header.From;
     45 import gov.nist.javax.sip.header.InReplyTo;
     46 import gov.nist.javax.sip.header.MaxForwards;
     47 import gov.nist.javax.sip.header.Priority;
     48 import gov.nist.javax.sip.header.ProxyAuthenticate;
     49 import gov.nist.javax.sip.header.ProxyAuthorization;
     50 import gov.nist.javax.sip.header.ProxyRequire;
     51 import gov.nist.javax.sip.header.ProxyRequireList;
     52 import gov.nist.javax.sip.header.RSeq;
     53 import gov.nist.javax.sip.header.RecordRouteList;
     54 import gov.nist.javax.sip.header.RetryAfter;
     55 import gov.nist.javax.sip.header.Route;
     56 import gov.nist.javax.sip.header.RouteList;
     57 import gov.nist.javax.sip.header.SIPETag;
     58 import gov.nist.javax.sip.header.SIPHeader;
     59 import gov.nist.javax.sip.header.SIPHeaderList;
     60 import gov.nist.javax.sip.header.SIPHeaderNamesCache;
     61 import gov.nist.javax.sip.header.SIPIfMatch;
     62 import gov.nist.javax.sip.header.Server;
     63 import gov.nist.javax.sip.header.Subject;
     64 import gov.nist.javax.sip.header.To;
     65 import gov.nist.javax.sip.header.Unsupported;
     66 import gov.nist.javax.sip.header.UserAgent;
     67 import gov.nist.javax.sip.header.Via;
     68 import gov.nist.javax.sip.header.ViaList;
     69 import gov.nist.javax.sip.header.WWWAuthenticate;
     70 import gov.nist.javax.sip.header.Warning;
     71 import gov.nist.javax.sip.parser.HeaderParser;
     72 import gov.nist.javax.sip.parser.ParserFactory;
     73 import gov.nist.javax.sip.parser.PipelinedMsgParser;
     74 import gov.nist.javax.sip.parser.StringMsgParser;
     75 
     76 import java.io.UnsupportedEncodingException;
     77 import java.lang.reflect.Field;
     78 import java.text.ParseException;
     79 import java.util.Collection;
     80 import java.util.Hashtable;
     81 import java.util.Iterator;
     82 import java.util.LinkedList;
     83 import java.util.List;
     84 import java.util.ListIterator;
     85 import java.util.concurrent.ConcurrentLinkedQueue;
     86 
     87 import javax.sip.InvalidArgumentException;
     88 import javax.sip.SipException;
     89 import javax.sip.header.AuthorizationHeader;
     90 import javax.sip.header.CSeqHeader;
     91 import javax.sip.header.CallIdHeader;
     92 import javax.sip.header.ContactHeader;
     93 import javax.sip.header.ContentDispositionHeader;
     94 import javax.sip.header.ContentEncodingHeader;
     95 import javax.sip.header.ContentLanguageHeader;
     96 import javax.sip.header.ContentLengthHeader;
     97 import javax.sip.header.ContentTypeHeader;
     98 import javax.sip.header.ExpiresHeader;
     99 import javax.sip.header.FromHeader;
    100 import javax.sip.header.Header;
    101 import javax.sip.header.MaxForwardsHeader;
    102 import javax.sip.header.RecordRouteHeader;
    103 import javax.sip.header.RouteHeader;
    104 import javax.sip.header.ToHeader;
    105 import javax.sip.header.ViaHeader;
    106 import javax.sip.message.Request;
    107 
    108 /*
    109  * Acknowledgements: Yanick Belanger sent in a patch for the right content length when the content
    110  * is a String. Bill Mccormick from Nortel Networks sent in a bug fix for setContent.
    111  *
    112  */
    113 /**
    114  * This is the main SIP Message structure.
    115  *
    116  * @see StringMsgParser
    117  * @see PipelinedMsgParser
    118  *
    119  * @version 1.2 $Revision: 1.53 $ $Date: 2009/12/16 14:58:40 $
    120  * @since 1.1
    121  *
    122  * @author M. Ranganathan <br/>
    123  *
    124  *
    125  */
    126 public abstract class SIPMessage extends MessageObject implements javax.sip.message.Message,
    127         MessageExt {
    128 
    129 	// JvB: use static here?
    130     private String contentEncodingCharset = MessageFactoryImpl.getDefaultContentEncodingCharset();
    131 
    132     /*
    133      * True if this is a null request.
    134      */
    135     protected boolean nullRequest;
    136 
    137     /**
    138      * unparsed headers
    139      */
    140     protected LinkedList<String> unrecognizedHeaders;
    141 
    142     /**
    143      * List of parsed headers (in the order they were added)
    144      */
    145     protected ConcurrentLinkedQueue<SIPHeader> headers;
    146 
    147     /**
    148      * Direct accessors for frequently accessed headers
    149      */
    150     protected From fromHeader;
    151 
    152     protected To toHeader;
    153 
    154     protected CSeq cSeqHeader;
    155 
    156     protected CallID callIdHeader;
    157 
    158     protected ContentLength contentLengthHeader;
    159 
    160     protected MaxForwards maxForwardsHeader;
    161 
    162     // Cumulative size of all the headers.
    163     protected int size;
    164 
    165     // Payload
    166     private String messageContent;
    167 
    168     private byte[] messageContentBytes;
    169 
    170     private Object messageContentObject;
    171 
    172     // Table of headers indexed by name.
    173     private Hashtable<String, SIPHeader> nameTable;
    174 
    175     /**
    176      * The application data pointer. This is un-interpreted by the stack. This is provided as a
    177      * convenient way of keeping book-keeping data for applications.
    178      */
    179     protected Object applicationData;
    180 
    181     /**
    182      * Return true if the header belongs only in a Request.
    183      *
    184      * @param sipHeader is the header to test.
    185      */
    186     public static boolean isRequestHeader(SIPHeader sipHeader) {
    187         return sipHeader instanceof AlertInfo || sipHeader instanceof InReplyTo
    188                 || sipHeader instanceof Authorization || sipHeader instanceof MaxForwards
    189                 || sipHeader instanceof UserAgent || sipHeader instanceof Priority
    190                 || sipHeader instanceof ProxyAuthorization || sipHeader instanceof ProxyRequire
    191                 || sipHeader instanceof ProxyRequireList || sipHeader instanceof Route
    192                 || sipHeader instanceof RouteList || sipHeader instanceof Subject
    193                 || sipHeader instanceof SIPIfMatch;
    194     }
    195 
    196     /**
    197      * Return true if the header belongs only in a response.
    198      *
    199      * @param sipHeader is the header to test.
    200      */
    201     public static boolean isResponseHeader(SIPHeader sipHeader) {
    202         return sipHeader instanceof ErrorInfo || sipHeader instanceof ProxyAuthenticate
    203                 || sipHeader instanceof Server || sipHeader instanceof Unsupported
    204                 || sipHeader instanceof RetryAfter || sipHeader instanceof Warning
    205                 || sipHeader instanceof WWWAuthenticate || sipHeader instanceof SIPETag
    206                 || sipHeader instanceof RSeq;
    207 
    208     }
    209 
    210     /**
    211      * Get the headers as a linked list of encoded Strings
    212      *
    213      * @return a linked list with each element of the list containing a string encoded header in
    214      *         canonical form.
    215      */
    216     public LinkedList<String> getMessageAsEncodedStrings() {
    217         LinkedList<String> retval = new LinkedList<String>();
    218         Iterator<SIPHeader> li = headers.iterator();
    219         while (li.hasNext()) {
    220             SIPHeader sipHeader = (SIPHeader) li.next();
    221             if (sipHeader instanceof SIPHeaderList) {
    222                 SIPHeaderList< ? > shl = (SIPHeaderList< ? >) sipHeader;
    223                 retval.addAll(shl.getHeadersAsEncodedStrings());
    224             } else {
    225                 retval.add(sipHeader.encode());
    226             }
    227         }
    228 
    229         return retval;
    230     }
    231 
    232     /**
    233      * Encode only the message and exclude the contents (for debugging);
    234      *
    235      * @return a string with all the headers encoded.
    236      */
    237     protected String encodeSIPHeaders() {
    238         StringBuffer encoding = new StringBuffer();
    239         Iterator<SIPHeader> it = this.headers.iterator();
    240 
    241         while (it.hasNext()) {
    242             SIPHeader siphdr = (SIPHeader) it.next();
    243             if (!(siphdr instanceof ContentLength))
    244                 siphdr.encode(encoding);
    245         }
    246 
    247         return contentLengthHeader.encode(encoding).append(NEWLINE).toString();
    248     }
    249 
    250     /**
    251      * Encode all the headers except the contents. For debug logging.
    252      */
    253     public abstract String encodeMessage();
    254 
    255     /**
    256      * Get A dialog identifier constructed from this messsage. This is an id that can be used to
    257      * identify dialogs.
    258      *
    259      * @param isServerTransaction is a flag that indicates whether this is a server transaction.
    260      */
    261     public abstract String getDialogId(boolean isServerTransaction);
    262 
    263     /**
    264      * Template match for SIP messages. The matchObj is a SIPMessage template to match against.
    265      * This method allows you to do pattern matching with incoming SIP messages. Null matches wild
    266      * card.
    267      *
    268      * @param other is the match template to match against.
    269      * @return true if a match occured and false otherwise.
    270      */
    271     public boolean match(Object other) {
    272         if (other == null)
    273             return true;
    274         if (!other.getClass().equals(this.getClass()))
    275             return false;
    276         SIPMessage matchObj = (SIPMessage) other;
    277         Iterator<SIPHeader> li = matchObj.getHeaders();
    278         while (li.hasNext()) {
    279             SIPHeader hisHeaders = (SIPHeader) li.next();
    280             List<SIPHeader> myHeaders = this.getHeaderList(hisHeaders.getHeaderName());
    281 
    282             // Could not find a header to match his header.
    283             if (myHeaders == null || myHeaders.size() == 0)
    284                 return false;
    285 
    286             if (hisHeaders instanceof SIPHeaderList) {
    287                 ListIterator< ? > outerIterator = ((SIPHeaderList< ? >) hisHeaders)
    288                         .listIterator();
    289                 while (outerIterator.hasNext()) {
    290                     SIPHeader hisHeader = (SIPHeader) outerIterator.next();
    291                     if (hisHeader instanceof ContentLength)
    292                         continue;
    293                     ListIterator< ? > innerIterator = myHeaders.listIterator();
    294                     boolean found = false;
    295                     while (innerIterator.hasNext()) {
    296                         SIPHeader myHeader = (SIPHeader) innerIterator.next();
    297                         if (myHeader.match(hisHeader)) {
    298                             found = true;
    299                             break;
    300                         }
    301                     }
    302                     if (!found)
    303                         return false;
    304                 }
    305             } else {
    306                 SIPHeader hisHeader = hisHeaders;
    307                 ListIterator<SIPHeader> innerIterator = myHeaders.listIterator();
    308                 boolean found = false;
    309                 while (innerIterator.hasNext()) {
    310                     SIPHeader myHeader = (SIPHeader) innerIterator.next();
    311                     if (myHeader.match(hisHeader)) {
    312                         found = true;
    313                         break;
    314                     }
    315                 }
    316                 if (!found)
    317                     return false;
    318             }
    319         }
    320         return true;
    321 
    322     }
    323 
    324     /**
    325      * Merge a request with a template
    326      *
    327      * @param template -- template to merge with.
    328      *
    329      */
    330     public void merge(Object template) {
    331         if (!template.getClass().equals(this.getClass()))
    332             throw new IllegalArgumentException("Bad class " + template.getClass());
    333         SIPMessage templateMessage = (SIPMessage) template;
    334         Object[] templateHeaders = templateMessage.headers.toArray();
    335         for (int i = 0; i < templateHeaders.length; i++) {
    336             SIPHeader hdr = (SIPHeader) templateHeaders[i];
    337             String hdrName = hdr.getHeaderName();
    338             List<SIPHeader> myHdrs = this.getHeaderList(hdrName);
    339             if (myHdrs == null) {
    340                 this.attachHeader(hdr);
    341             } else {
    342                 ListIterator<SIPHeader> it = myHdrs.listIterator();
    343                 while (it.hasNext()) {
    344                     SIPHeader sipHdr = (SIPHeader) it.next();
    345                     sipHdr.merge(hdr);
    346                 }
    347             }
    348         }
    349 
    350     }
    351 
    352     /**
    353      * Encode this message as a string. This is more efficient when the payload is a string
    354      * (rather than a binary array of bytes). If the payload cannot be encoded as a UTF-8 string
    355      * then it is simply ignored (will not appear in the encoded message).
    356      *
    357      * @return The Canonical String representation of the message (including the canonical string
    358      *         representation of the SDP payload if it exists).
    359      */
    360     public String encode() {
    361         StringBuffer encoding = new StringBuffer();
    362         Iterator<SIPHeader> it = this.headers.iterator();
    363 
    364         while (it.hasNext()) {
    365             SIPHeader siphdr = (SIPHeader) it.next();
    366             if (!(siphdr instanceof ContentLength))
    367                 encoding.append(siphdr.encode());
    368         }
    369         // Append the unrecognized headers. Headers that are not
    370         // recognized are passed through unchanged.
    371         for (String unrecognized : this.unrecognizedHeaders) {
    372             encoding.append(unrecognized).append(NEWLINE);
    373         }
    374 
    375         encoding.append(contentLengthHeader.encode()).append(NEWLINE);
    376 
    377         if (this.messageContentObject != null) {
    378             String mbody = this.getContent().toString();
    379 
    380             encoding.append(mbody);
    381         } else if (this.messageContent != null || this.messageContentBytes != null) {
    382 
    383             String content = null;
    384             try {
    385                 if (messageContent != null)
    386                     content = messageContent;
    387                 else {
    388                 	// JvB: Check for 'charset' parameter which overrides the default UTF-8
    389                     content = new String(messageContentBytes, getCharset() );
    390                 }
    391             } catch (UnsupportedEncodingException ex) {
    392             	InternalErrorHandler.handleException(ex);
    393             }
    394 
    395             encoding.append(content);
    396         }
    397         return encoding.toString();
    398     }
    399 
    400     /**
    401      * Encode the message as a byte array. Use this when the message payload is a binary byte
    402      * array.
    403      *
    404      * @return The Canonical byte array representation of the message (including the canonical
    405      *         byte array representation of the SDP payload if it exists all in one contiguous
    406      *         byte array).
    407      */
    408     public byte[] encodeAsBytes(String transport) {
    409         if (this instanceof SIPRequest && ((SIPRequest) this).isNullRequest()) {
    410             return "\r\n\r\n".getBytes();
    411         }
    412         // JvB: added to fix case where application provides the wrong transport
    413         // in the topmost Via header
    414         ViaHeader topVia = (ViaHeader) this.getHeader(ViaHeader.NAME);
    415         try {
    416             topVia.setTransport(transport);
    417         } catch (ParseException e) {
    418             InternalErrorHandler.handleException(e);
    419         }
    420 
    421         StringBuffer encoding = new StringBuffer();
    422         synchronized (this.headers) {
    423             Iterator<SIPHeader> it = this.headers.iterator();
    424 
    425             while (it.hasNext()) {
    426                 SIPHeader siphdr = (SIPHeader) it.next();
    427                 if (!(siphdr instanceof ContentLength))
    428                     siphdr.encode(encoding);
    429 
    430             }
    431         }
    432         contentLengthHeader.encode(encoding);
    433         encoding.append(NEWLINE);
    434 
    435         byte[] retval = null;
    436         byte[] content = this.getRawContent();
    437         if (content != null) {
    438             // Append the content
    439 
    440             byte[] msgarray = null;
    441             try {
    442                 msgarray = encoding.toString().getBytes( getCharset() );
    443             } catch (UnsupportedEncodingException ex) {
    444                 InternalErrorHandler.handleException(ex);
    445             }
    446 
    447             retval = new byte[msgarray.length + content.length];
    448             System.arraycopy(msgarray, 0, retval, 0, msgarray.length);
    449             System.arraycopy(content, 0, retval, msgarray.length, content.length);
    450         } else {
    451             // Message content does not exist.
    452 
    453             try {
    454                 retval = encoding.toString().getBytes( getCharset() );
    455             } catch (UnsupportedEncodingException ex) {
    456                 InternalErrorHandler.handleException(ex);
    457             }
    458         }
    459         return retval;
    460     }
    461 
    462     /**
    463      * clone this message (create a new deep physical copy). All headers in the message are
    464      * cloned. You can modify the cloned copy without affecting the original. The content is
    465      * handled as follows: If the content is a String, or a byte array, a new copy of the content
    466      * is allocated and copied over. If the content is an Object that supports the clone method,
    467      * then the clone method is invoked and the cloned content is the new content. Otherwise, the
    468      * content of the new message is set equal to the old one.
    469      *
    470      * @return A cloned copy of this object.
    471      */
    472     public Object clone() {
    473         SIPMessage retval = (SIPMessage) super.clone();
    474         retval.nameTable = new Hashtable<String, SIPHeader>();
    475         retval.fromHeader = null;
    476         retval.toHeader = null;
    477         retval.cSeqHeader = null;
    478         retval.callIdHeader = null;
    479         retval.contentLengthHeader = null;
    480         retval.maxForwardsHeader = null;
    481         if (this.headers != null) {
    482             retval.headers = new ConcurrentLinkedQueue<SIPHeader>();
    483             for (Iterator<SIPHeader> iter = headers.iterator(); iter.hasNext();) {
    484                 SIPHeader hdr = (SIPHeader) iter.next();
    485                 retval.attachHeader((SIPHeader) hdr.clone());
    486             }
    487 
    488         }
    489         if (this.messageContentBytes != null)
    490             retval.messageContentBytes = (byte[]) this.messageContentBytes.clone();
    491         if (this.messageContentObject != null)
    492             retval.messageContentObject = makeClone(messageContentObject);
    493         retval.unrecognizedHeaders = this.unrecognizedHeaders;
    494         return retval;
    495     }
    496 
    497     /**
    498      * Get the string representation of this header (for pretty printing the generated structure).
    499      *
    500      * @return Formatted string representation of the object. Note that this is NOT the same as
    501      *         encode(). This is used mainly for debugging purposes.
    502      */
    503     public String debugDump() {
    504         stringRepresentation = "";
    505         sprint("SIPMessage:");
    506         sprint("{");
    507         try {
    508 
    509             Field[] fields = this.getClass().getDeclaredFields();
    510             for (int i = 0; i < fields.length; i++) {
    511                 Field f = fields[i];
    512                 Class< ? > fieldType = f.getType();
    513                 String fieldName = f.getName();
    514                 if (f.get(this) != null && SIPHeader.class.isAssignableFrom(fieldType)
    515                         && fieldName.compareTo("headers") != 0) {
    516                     sprint(fieldName + "=");
    517                     sprint(((SIPHeader) f.get(this)).debugDump());
    518                 }
    519             }
    520         } catch (Exception ex) {
    521             InternalErrorHandler.handleException(ex);
    522         }
    523 
    524         sprint("List of headers : ");
    525         sprint(headers.toString());
    526         sprint("messageContent = ");
    527         sprint("{");
    528         sprint(messageContent);
    529         sprint("}");
    530         if (this.getContent() != null) {
    531             sprint(this.getContent().toString());
    532         }
    533         sprint("}");
    534         return stringRepresentation;
    535     }
    536 
    537     /**
    538      * Constructor: Initializes lists and list headers. All the headers for which there can be
    539      * multiple occurances in a message are derived from the SIPHeaderListClass. All singleton
    540      * headers are derived from SIPHeader class.
    541      */
    542     public SIPMessage() {
    543         this.unrecognizedHeaders = new LinkedList<String>();
    544         this.headers = new ConcurrentLinkedQueue<SIPHeader>();
    545         nameTable = new Hashtable<String, SIPHeader>();
    546         try {
    547             this.attachHeader(new ContentLength(0), false);
    548         } catch (Exception ex) {
    549         }
    550     }
    551 
    552     /**
    553      * Attach a header and die if you get a duplicate header exception.
    554      *
    555      * @param h SIPHeader to attach.
    556      */
    557     private void attachHeader(SIPHeader h) {
    558         if (h == null)
    559             throw new IllegalArgumentException("null header!");
    560         try {
    561             if (h instanceof SIPHeaderList) {
    562                 SIPHeaderList< ? > hl = (SIPHeaderList< ? >) h;
    563                 if (hl.isEmpty()) {
    564                     return;
    565                 }
    566             }
    567             attachHeader(h, false, false);
    568         } catch (SIPDuplicateHeaderException ex) {
    569             // InternalErrorHandler.handleException(ex);
    570         }
    571     }
    572 
    573     /**
    574      * Attach a header (replacing the original header).
    575      *
    576      * @param sipHeader SIPHeader that replaces a header of the same type.
    577      */
    578     public void setHeader(Header sipHeader) {
    579         SIPHeader header = (SIPHeader) sipHeader;
    580         if (header == null)
    581             throw new IllegalArgumentException("null header!");
    582         try {
    583             if (header instanceof SIPHeaderList) {
    584                 SIPHeaderList< ? > hl = (SIPHeaderList< ? >) header;
    585                 // Ignore empty lists.
    586                 if (hl.isEmpty())
    587                     return;
    588             }
    589             this.removeHeader(header.getHeaderName());
    590             attachHeader(header, true, false);
    591         } catch (SIPDuplicateHeaderException ex) {
    592             InternalErrorHandler.handleException(ex);
    593         }
    594     }
    595 
    596     /**
    597      * Set a header from a linked list of headers.
    598      *
    599      * @param headers -- a list of headers to set.
    600      */
    601     public void setHeaders(java.util.List<SIPHeader> headers) {
    602         ListIterator<SIPHeader> listIterator = headers.listIterator();
    603         while (listIterator.hasNext()) {
    604             SIPHeader sipHeader = (SIPHeader) listIterator.next();
    605             try {
    606                 this.attachHeader(sipHeader, false);
    607             } catch (SIPDuplicateHeaderException ex) {
    608             }
    609         }
    610     }
    611 
    612     /**
    613      * Attach a header to the end of the existing headers in this SIPMessage structure. This is
    614      * equivalent to the attachHeader(SIPHeader,replaceflag,false); which is the normal way in
    615      * which headers are attached. This was added in support of JAIN-SIP.
    616      *
    617      * @param h header to attach.
    618      * @param replaceflag if true then replace a header if it exists.
    619      * @throws SIPDuplicateHeaderException If replaceFlag is false and only a singleton header is
    620      *         allowed (fpr example CSeq).
    621      */
    622     public void attachHeader(SIPHeader h, boolean replaceflag) throws SIPDuplicateHeaderException {
    623         this.attachHeader(h, replaceflag, false);
    624     }
    625 
    626     /**
    627      * Attach the header to the SIP Message structure at a specified position in its list of
    628      * headers.
    629      *
    630      * @param header Header to attach.
    631      * @param replaceFlag If true then replace the existing header.
    632      * @param top Location in the header list to insert the header.
    633      * @exception SIPDuplicateHeaderException if the header is of a type that cannot tolerate
    634      *            duplicates and one of this type already exists (e.g. CSeq header).
    635      * @throws IndexOutOfBoundsException If the index specified is greater than the number of
    636      *         headers that are in this message.
    637      */
    638 
    639     public void attachHeader(SIPHeader header, boolean replaceFlag, boolean top)
    640             throws SIPDuplicateHeaderException {
    641         if (header == null) {
    642             throw new NullPointerException("null header");
    643         }
    644 
    645         SIPHeader h;
    646 
    647         if (ListMap.hasList(header) && !SIPHeaderList.class.isAssignableFrom(header.getClass())) {
    648             SIPHeaderList<SIPHeader> hdrList = ListMap.getList(header);
    649             hdrList.add(header);
    650             h = hdrList;
    651         } else {
    652             h = header;
    653         }
    654 
    655         String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(h.getName());
    656         if (replaceFlag) {
    657             nameTable.remove(headerNameLowerCase);
    658         } else if (nameTable.containsKey(headerNameLowerCase) && !(h instanceof SIPHeaderList)) {
    659             if (h instanceof ContentLength) {
    660                 try {
    661                     ContentLength cl = (ContentLength) h;
    662                     contentLengthHeader.setContentLength(cl.getContentLength());
    663                 } catch (InvalidArgumentException e) {
    664                 }
    665             }
    666             // Just ignore duplicate header.
    667             return;
    668         }
    669 
    670         SIPHeader originalHeader = (SIPHeader) getHeader(header.getName());
    671 
    672         // Delete the original header from our list structure.
    673         if (originalHeader != null) {
    674             Iterator<SIPHeader> li = headers.iterator();
    675             while (li.hasNext()) {
    676                 SIPHeader next = (SIPHeader) li.next();
    677                 if (next.equals(originalHeader)) {
    678                     li.remove();
    679                 }
    680             }
    681         }
    682 
    683         if (!nameTable.containsKey(headerNameLowerCase)) {
    684             nameTable.put(headerNameLowerCase, h);
    685             headers.add(h);
    686         } else {
    687             if (h instanceof SIPHeaderList) {
    688                 SIPHeaderList< ? > hdrlist = (SIPHeaderList< ? >) nameTable
    689                         .get(headerNameLowerCase);
    690                 if (hdrlist != null)
    691                     hdrlist.concatenate((SIPHeaderList) h, top);
    692                 else
    693                     nameTable.put(headerNameLowerCase, h);
    694             } else {
    695                 nameTable.put(headerNameLowerCase, h);
    696             }
    697         }
    698 
    699         // Direct accessor fields for frequently accessed headers.
    700         if (h instanceof From) {
    701             this.fromHeader = (From) h;
    702         } else if (h instanceof ContentLength) {
    703             this.contentLengthHeader = (ContentLength) h;
    704         } else if (h instanceof To) {
    705             this.toHeader = (To) h;
    706         } else if (h instanceof CSeq) {
    707             this.cSeqHeader = (CSeq) h;
    708         } else if (h instanceof CallID) {
    709             this.callIdHeader = (CallID) h;
    710         } else if (h instanceof MaxForwards) {
    711             this.maxForwardsHeader = (MaxForwards) h;
    712         }
    713 
    714     }
    715 
    716     /**
    717      * Remove a header given its name. If multiple headers of a given name are present then the
    718      * top flag determines which end to remove headers from.
    719      *
    720      * @param headerName is the name of the header to remove.
    721      * @param top -- flag that indicates which end of header list to process.
    722      */
    723     public void removeHeader(String headerName, boolean top) {
    724 
    725         String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName);
    726         SIPHeader toRemove = (SIPHeader) nameTable.get(headerNameLowerCase);
    727         // nothing to do then we are done.
    728         if (toRemove == null)
    729             return;
    730         if (toRemove instanceof SIPHeaderList) {
    731             SIPHeaderList< ? > hdrList = (SIPHeaderList< ? >) toRemove;
    732             if (top)
    733                 hdrList.removeFirst();
    734             else
    735                 hdrList.removeLast();
    736             // Clean up empty list
    737             if (hdrList.isEmpty()) {
    738                 Iterator<SIPHeader> li = this.headers.iterator();
    739                 while (li.hasNext()) {
    740                     SIPHeader sipHeader = (SIPHeader) li.next();
    741                     if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase))
    742                         li.remove();
    743                 }
    744 
    745                 // JvB: also remove it from the nameTable! Else NPE in
    746                 // DefaultRouter
    747                 nameTable.remove(headerNameLowerCase);
    748             }
    749         } else {
    750             this.nameTable.remove(headerNameLowerCase);
    751             if (toRemove instanceof From) {
    752                 this.fromHeader = null;
    753             } else if (toRemove instanceof To) {
    754                 this.toHeader = null;
    755             } else if (toRemove instanceof CSeq) {
    756                 this.cSeqHeader = null;
    757             } else if (toRemove instanceof CallID) {
    758                 this.callIdHeader = null;
    759             } else if (toRemove instanceof MaxForwards) {
    760                 this.maxForwardsHeader = null;
    761             } else if (toRemove instanceof ContentLength) {
    762                 this.contentLengthHeader = null;
    763             }
    764             Iterator<SIPHeader> li = this.headers.iterator();
    765             while (li.hasNext()) {
    766                 SIPHeader sipHeader = (SIPHeader) li.next();
    767                 if (sipHeader.getName().equalsIgnoreCase(headerName))
    768                     li.remove();
    769             }
    770         }
    771 
    772     }
    773 
    774     /**
    775      * Remove all headers given its name.
    776      *
    777      * @param headerName is the name of the header to remove.
    778      */
    779     public void removeHeader(String headerName) {
    780 
    781         if (headerName == null)
    782             throw new NullPointerException("null arg");
    783         String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName);
    784         SIPHeader removed = (SIPHeader) nameTable.remove(headerNameLowerCase);
    785         // nothing to do then we are done.
    786         if (removed == null)
    787             return;
    788 
    789         // Remove the fast accessor fields.
    790         if (removed instanceof From) {
    791             this.fromHeader = null;
    792         } else if (removed instanceof To) {
    793             this.toHeader = null;
    794         } else if (removed instanceof CSeq) {
    795             this.cSeqHeader = null;
    796         } else if (removed instanceof CallID) {
    797             this.callIdHeader = null;
    798         } else if (removed instanceof MaxForwards) {
    799             this.maxForwardsHeader = null;
    800         } else if (removed instanceof ContentLength) {
    801             this.contentLengthHeader = null;
    802         }
    803 
    804         Iterator<SIPHeader> li = this.headers.iterator();
    805         while (li.hasNext()) {
    806             SIPHeader sipHeader = (SIPHeader) li.next();
    807             if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase))
    808                 li.remove();
    809 
    810         }
    811     }
    812 
    813     /**
    814      * Generate (compute) a transaction ID for this SIP message.
    815      *
    816      * @return A string containing the concatenation of various portions of the From,To,Via and
    817      *         RequestURI portions of this message as specified in RFC 2543: All responses to a
    818      *         request contain the same values in the Call-ID, CSeq, To, and From fields (with the
    819      *         possible addition of a tag in the To field (section 10.43)). This allows responses
    820      *         to be matched with requests. Incorporates a bug fix for a bug sent in by Gordon
    821      *         Ledgard of IPera for generating transactionIDs when no port is present in the via
    822      *         header. Incorporates a bug fix for a bug report sent in by Chris Mills of Nortel
    823      *         Networks (converts to lower case when returning the transaction identifier).
    824      *
    825      * @return a string that can be used as a transaction identifier for this message. This can be
    826      *         used for matching responses and requests (i.e. an outgoing request and its matching
    827      *         response have the same computed transaction identifier).
    828      */
    829     public String getTransactionId() {
    830         Via topVia = null;
    831         if (!this.getViaHeaders().isEmpty()) {
    832             topVia = (Via) this.getViaHeaders().getFirst();
    833         }
    834         // Have specified a branch Identifier so we can use it to identify
    835         // the transaction. BranchId is not case sensitive.
    836         // Branch Id prefix is not case sensitive.
    837         if (topVia != null
    838                 && topVia.getBranch() != null
    839                 && topVia.getBranch().toUpperCase().startsWith(
    840                         SIPConstants.BRANCH_MAGIC_COOKIE_UPPER_CASE)) {
    841             // Bis 09 compatible branch assignment algorithm.
    842             // implies that the branch id can be used as a transaction
    843             // identifier.
    844             if (this.getCSeq().getMethod().equals(Request.CANCEL))
    845                 return (topVia.getBranch() + ":" + this.getCSeq().getMethod()).toLowerCase();
    846             else
    847                 return topVia.getBranch().toLowerCase();
    848         } else {
    849             // Old style client so construct the transaction identifier
    850             // from various fields of the request.
    851             StringBuffer retval = new StringBuffer();
    852             From from = (From) this.getFrom();
    853             To to = (To) this.getTo();
    854             // String hpFrom = from.getUserAtHostPort();
    855             // retval.append(hpFrom).append(":");
    856             if (from.hasTag())
    857                 retval.append(from.getTag()).append("-");
    858             // String hpTo = to.getUserAtHostPort();
    859             // retval.append(hpTo).append(":");
    860             String cid = this.callIdHeader.getCallId();
    861             retval.append(cid).append("-");
    862             retval.append(this.cSeqHeader.getSequenceNumber()).append("-").append(
    863                     this.cSeqHeader.getMethod());
    864             if (topVia != null) {
    865                 retval.append("-").append(topVia.getSentBy().encode());
    866                 if (!topVia.getSentBy().hasPort()) {
    867                     retval.append("-").append(5060);
    868                 }
    869             }
    870             if (this.getCSeq().getMethod().equals(Request.CANCEL)) {
    871                 retval.append(Request.CANCEL);
    872             }
    873             return retval.toString().toLowerCase().replace(":", "-").replace("@", "-")
    874                     + Utils.getSignature();
    875         }
    876     }
    877 
    878     /**
    879      * Override the hashcode method ( see issue # 55 ) Note that if you try to use this method
    880      * before you assemble a valid request, you will get a constant ( -1 ). Beware of placing any
    881      * half formed requests in a table.
    882      */
    883     public int hashCode() {
    884         if (this.callIdHeader == null)
    885             throw new RuntimeException(
    886                     "Invalid message! Cannot compute hashcode! call-id header is missing !");
    887         else
    888             return this.callIdHeader.getCallId().hashCode();
    889     }
    890 
    891     /**
    892      * Return true if this message has a body.
    893      */
    894     public boolean hasContent() {
    895         return messageContent != null || messageContentBytes != null;
    896     }
    897 
    898     /**
    899      * Return an iterator for the list of headers in this message.
    900      *
    901      * @return an Iterator for the headers of this message.
    902      */
    903     public Iterator<SIPHeader> getHeaders() {
    904         return headers.iterator();
    905     }
    906 
    907     /**
    908      * Get the first header of the given name.
    909      *
    910      * @return header -- the first header of the given name.
    911      */
    912     public Header getHeader(String headerName) {
    913         return getHeaderLowerCase(SIPHeaderNamesCache.toLowerCase(headerName));
    914     }
    915 
    916     private Header getHeaderLowerCase(String lowerCaseHeaderName) {
    917         if (lowerCaseHeaderName == null)
    918             throw new NullPointerException("bad name");
    919         SIPHeader sipHeader = (SIPHeader) nameTable.get(lowerCaseHeaderName);
    920         if (sipHeader instanceof SIPHeaderList)
    921             return (Header) ((SIPHeaderList) sipHeader).getFirst();
    922         else
    923             return (Header) sipHeader;
    924     }
    925 
    926     /**
    927      * Get the contentType header (null if one does not exist).
    928      *
    929      * @return contentType header
    930      */
    931 
    932     public ContentType getContentTypeHeader() {
    933         return (ContentType) getHeaderLowerCase(CONTENT_TYPE_LOWERCASE);
    934     }
    935 
    936     private static final String CONTENT_TYPE_LOWERCASE = SIPHeaderNamesCache
    937     .toLowerCase(ContentTypeHeader.NAME);
    938 
    939 
    940     /**
    941      * Get the contentLength header.
    942      */
    943     public ContentLengthHeader getContentLengthHeader() {
    944         return this.getContentLength();
    945     }
    946 
    947 
    948     /**
    949      * Get the from header.
    950      *
    951      * @return -- the from header.
    952      */
    953     public FromHeader getFrom() {
    954         return (FromHeader) fromHeader;
    955     }
    956 
    957     /**
    958      * Get the ErrorInfo list of headers (null if one does not exist).
    959      *
    960      * @return List containing ErrorInfo headers.
    961      */
    962     public ErrorInfoList getErrorInfoHeaders() {
    963         return (ErrorInfoList) getSIPHeaderListLowerCase(ERROR_LOWERCASE);
    964     }
    965 
    966     private static final String ERROR_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ErrorInfo.NAME);
    967 
    968     /**
    969      * Get the Contact list of headers (null if one does not exist).
    970      *
    971      * @return List containing Contact headers.
    972      */
    973     public ContactList getContactHeaders() {
    974         return (ContactList) this.getSIPHeaderListLowerCase(CONTACT_LOWERCASE);
    975     }
    976 
    977     private static final String CONTACT_LOWERCASE = SIPHeaderNamesCache
    978             .toLowerCase(ContactHeader.NAME);
    979 
    980     /**
    981      * Get the contact header ( the first contact header) which is all we need for the most part.
    982      *
    983      */
    984     public Contact getContactHeader() {
    985         ContactList clist = this.getContactHeaders();
    986         if (clist != null) {
    987             return (Contact) clist.getFirst();
    988 
    989         } else {
    990             return null;
    991         }
    992     }
    993 
    994     /**
    995      * Get the Via list of headers (null if one does not exist).
    996      *
    997      * @return List containing Via headers.
    998      */
    999     public ViaList getViaHeaders() {
   1000         return (ViaList) getSIPHeaderListLowerCase(VIA_LOWERCASE);
   1001     }
   1002 
   1003     private static final String VIA_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ViaHeader.NAME);
   1004 
   1005     /**
   1006      * Set A list of via headers.
   1007      *
   1008      * @param viaList a list of via headers to add.
   1009      */
   1010     public void setVia(java.util.List viaList) {
   1011         ViaList vList = new ViaList();
   1012         ListIterator it = viaList.listIterator();
   1013         while (it.hasNext()) {
   1014             Via via = (Via) it.next();
   1015             vList.add(via);
   1016         }
   1017         this.setHeader(vList);
   1018     }
   1019 
   1020     /**
   1021      * Set the header given a list of headers.
   1022      *
   1023      * @param sipHeaderList a headerList to set
   1024      */
   1025 
   1026     public void setHeader(SIPHeaderList<Via> sipHeaderList) {
   1027         this.setHeader((Header) sipHeaderList);
   1028     }
   1029 
   1030     /**
   1031      * Get the topmost via header.
   1032      *
   1033      * @return the top most via header if one exists or null if none exists.
   1034      */
   1035     public Via getTopmostVia() {
   1036         if (this.getViaHeaders() == null)
   1037             return null;
   1038         else
   1039             return (Via) (getViaHeaders().getFirst());
   1040     }
   1041 
   1042     /**
   1043      * Get the CSeq list of header (null if one does not exist).
   1044      *
   1045      * @return CSeq header
   1046      */
   1047     public CSeqHeader getCSeq() {
   1048         return (CSeqHeader) cSeqHeader;
   1049     }
   1050 
   1051     /**
   1052      * Get the Authorization header (null if one does not exist).
   1053      *
   1054      * @return Authorization header.
   1055      */
   1056     public Authorization getAuthorization() {
   1057         return (Authorization) getHeaderLowerCase(AUTHORIZATION_LOWERCASE);
   1058     }
   1059 
   1060     private static final String AUTHORIZATION_LOWERCASE = SIPHeaderNamesCache
   1061             .toLowerCase(AuthorizationHeader.NAME);
   1062 
   1063     /**
   1064      * Get the MaxForwards header (null if one does not exist).
   1065      *
   1066      * @return Max-Forwards header
   1067      */
   1068 
   1069     public MaxForwardsHeader getMaxForwards() {
   1070         return maxForwardsHeader;
   1071     }
   1072 
   1073     /**
   1074      * Set the max forwards header.
   1075      *
   1076      * @param maxForwards is the MaxForwardsHeader to set.
   1077      */
   1078     public void setMaxForwards(MaxForwardsHeader maxForwards) {
   1079         this.setHeader(maxForwards);
   1080     }
   1081 
   1082     /**
   1083      * Get the Route List of headers (null if one does not exist).
   1084      *
   1085      * @return List containing Route headers
   1086      */
   1087     public RouteList getRouteHeaders() {
   1088         return (RouteList) getSIPHeaderListLowerCase(ROUTE_LOWERCASE);
   1089     }
   1090 
   1091     private static final String ROUTE_LOWERCASE = SIPHeaderNamesCache
   1092             .toLowerCase(RouteHeader.NAME);
   1093 
   1094     /**
   1095      * Get the CallID header (null if one does not exist)
   1096      *
   1097      * @return Call-ID header .
   1098      */
   1099     public CallIdHeader getCallId() {
   1100         return callIdHeader;
   1101     }
   1102 
   1103     /**
   1104      * Set the call id header.
   1105      *
   1106      * @param callId call idHeader (what else could it be?)
   1107      */
   1108     public void setCallId(CallIdHeader callId) {
   1109         this.setHeader(callId);
   1110     }
   1111 
   1112     /**
   1113      * Get the CallID header (null if one does not exist)
   1114      *
   1115      * @param callId -- the call identifier to be assigned to the call id header
   1116      */
   1117     public void setCallId(String callId) throws java.text.ParseException {
   1118         if (callIdHeader == null) {
   1119             this.setHeader(new CallID());
   1120         }
   1121         callIdHeader.setCallId(callId);
   1122     }
   1123 
   1124     /**
   1125      * Get the RecordRoute header list (null if one does not exist).
   1126      *
   1127      * @return Record-Route header
   1128      */
   1129     public RecordRouteList getRecordRouteHeaders() {
   1130         return (RecordRouteList) this.getSIPHeaderListLowerCase(RECORDROUTE_LOWERCASE);
   1131     }
   1132 
   1133     private static final String RECORDROUTE_LOWERCASE = SIPHeaderNamesCache
   1134             .toLowerCase(RecordRouteHeader.NAME);
   1135 
   1136     /**
   1137      * Get the To header (null if one does not exist).
   1138      *
   1139      * @return To header
   1140      */
   1141     public ToHeader getTo() {
   1142         return (ToHeader) toHeader;
   1143     }
   1144 
   1145     public void setTo(ToHeader to) {
   1146         this.setHeader(to);
   1147     }
   1148 
   1149     public void setFrom(FromHeader from) {
   1150         this.setHeader(from);
   1151 
   1152     }
   1153 
   1154     /**
   1155      * Get the ContentLength header (null if one does not exist).
   1156      *
   1157      * @return content-length header.
   1158      */
   1159     public ContentLengthHeader getContentLength() {
   1160         return this.contentLengthHeader;
   1161     }
   1162 
   1163     /**
   1164      * Get the message body as a string. If the message contains a content type header with a
   1165      * specified charset, and if the payload has been read as a byte array, then it is returned
   1166      * encoded into this charset.
   1167      *
   1168      * @return Message body (as a string)
   1169      * @throws UnsupportedEncodingException if the platform does not support the charset specified
   1170      *         in the content type header.
   1171      *
   1172      */
   1173     public String getMessageContent() throws UnsupportedEncodingException {
   1174         if (this.messageContent == null && this.messageContentBytes == null)
   1175             return null;
   1176         else if (this.messageContent == null) {
   1177             this.messageContent = new String(messageContentBytes, getCharset() );
   1178         }
   1179         return this.messageContent;
   1180     }
   1181 
   1182     /**
   1183      * Get the message content as an array of bytes. If the payload has been read as a String then
   1184      * it is decoded using the charset specified in the content type header if it exists.
   1185      * Otherwise, it is encoded using the default encoding which is UTF-8.
   1186      *
   1187      * @return an array of bytes that is the message payload.
   1188      */
   1189     public byte[] getRawContent() {
   1190         try {
   1191             if ( this.messageContentBytes != null ) {
   1192                 // return messageContentBytes;
   1193             } else if (this.messageContentObject != null) {
   1194                 String messageContent = this.messageContentObject.toString();
   1195                 this.messageContentBytes = messageContent.getBytes( getCharset() );
   1196             } else if (this.messageContent != null) {
   1197             	this.messageContentBytes = messageContent.getBytes( getCharset() );
   1198             }
   1199             return this.messageContentBytes;
   1200         } catch (UnsupportedEncodingException ex) {
   1201             InternalErrorHandler.handleException(ex);
   1202             return null;
   1203         }
   1204     }
   1205 
   1206     /**
   1207      * Set the message content given type and subtype.
   1208      *
   1209      * @param type is the message type (eg. application)
   1210      * @param subType is the message sybtype (eg. sdp)
   1211      * @param messageContent is the messge content as a string.
   1212      */
   1213     public void setMessageContent(String type, String subType, String messageContent) {
   1214         if (messageContent == null)
   1215             throw new IllegalArgumentException("messgeContent is null");
   1216         ContentType ct = new ContentType(type, subType);
   1217         this.setHeader(ct);
   1218         this.messageContent = messageContent;
   1219         this.messageContentBytes = null;
   1220         this.messageContentObject = null;
   1221         // Could be double byte so we need to compute length
   1222         // after converting to byte[]
   1223         computeContentLength(messageContent);
   1224     }
   1225 
   1226     /**
   1227      * Set the message content after converting the given object to a String.
   1228      *
   1229      * @param content -- content to set.
   1230      * @param contentTypeHeader -- content type header corresponding to content.
   1231      */
   1232     public void setContent(Object content, ContentTypeHeader contentTypeHeader)
   1233             throws ParseException {
   1234         if (content == null)
   1235             throw new NullPointerException("null content");
   1236         this.setHeader(contentTypeHeader);
   1237 
   1238         this.messageContent = null;
   1239         this.messageContentBytes = null;
   1240         this.messageContentObject = null;
   1241 
   1242         if (content instanceof String) {
   1243             this.messageContent = (String) content;
   1244         } else if (content instanceof byte[]) {
   1245             this.messageContentBytes = (byte[]) content;
   1246         } else
   1247             this.messageContentObject = content;
   1248 
   1249         computeContentLength(content);
   1250     }
   1251 
   1252     /**
   1253      * Get the content (body) of the message.
   1254      *
   1255      * @return the content of the sip message.
   1256      */
   1257     public Object getContent() {
   1258         if (this.messageContentObject != null)
   1259             return messageContentObject;
   1260         else if (this.messageContent != null)
   1261             return this.messageContent;
   1262         else if (this.messageContentBytes != null)
   1263             return this.messageContentBytes;
   1264         else
   1265             return null;
   1266     }
   1267 
   1268     /**
   1269      * Set the message content for a given type and subtype.
   1270      *
   1271      * @param type is the messge type.
   1272      * @param subType is the message subType.
   1273      * @param messageContent is the message content as a byte array.
   1274      */
   1275     public void setMessageContent(String type, String subType, byte[] messageContent) {
   1276         ContentType ct = new ContentType(type, subType);
   1277         this.setHeader(ct);
   1278         this.setMessageContent(messageContent);
   1279 
   1280         computeContentLength(messageContent);
   1281     }
   1282 
   1283     /**
   1284      * Set the message content for this message.
   1285      *
   1286      * @param content Message body as a string.
   1287      */
   1288     public void setMessageContent(String content, boolean strict, boolean computeContentLength, int givenLength)
   1289             throws ParseException {
   1290         // Note that that this could be a double byte character
   1291         // set - bug report by Masafumi Watanabe
   1292         computeContentLength(content);
   1293         if ((!computeContentLength)) {
   1294             if ( (!strict && this.contentLengthHeader.getContentLength() != givenLength)
   1295                     || this.contentLengthHeader.getContentLength() < givenLength) {
   1296                 throw new ParseException("Invalid content length "
   1297                         + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0);
   1298             }
   1299         }
   1300 
   1301         messageContent = content;
   1302         messageContentBytes = null;
   1303         messageContentObject = null;
   1304     }
   1305 
   1306     /**
   1307      * Set the message content as an array of bytes.
   1308      *
   1309      * @param content is the content of the message as an array of bytes.
   1310      */
   1311     public void setMessageContent(byte[] content) {
   1312         computeContentLength(content);
   1313 
   1314         messageContentBytes = content;
   1315         messageContent = null;
   1316         messageContentObject = null;
   1317     }
   1318 
   1319     /**
   1320      * Method to set the content - called by the parser
   1321      *
   1322      * @param content
   1323      * @throws ParseException
   1324      */
   1325     public void setMessageContent(byte[] content, boolean computeContentLength, int givenLength)
   1326             throws ParseException {
   1327         computeContentLength(content);
   1328         if ((!computeContentLength) && this.contentLengthHeader.getContentLength() < givenLength) {
   1329             // System.out.println("!!!!!!!!!!! MISMATCH !!!!!!!!!!!");
   1330             throw new ParseException("Invalid content length "
   1331                     + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0);
   1332         }
   1333         messageContentBytes = content;
   1334         messageContent = null;
   1335         messageContentObject = null;
   1336     }
   1337 
   1338     /**
   1339      * Compute and set the Content-length header based on the given content object.
   1340      *
   1341      * @param content is the content, as String, array of bytes, or other object.
   1342      */
   1343     private void computeContentLength(Object content) {
   1344         int length = 0;
   1345         if (content != null) {
   1346             if (content instanceof String) {
   1347                 try {
   1348                     length = ((String) content).getBytes( getCharset() ).length;
   1349                 } catch (UnsupportedEncodingException ex) {
   1350                     InternalErrorHandler.handleException(ex);
   1351                 }
   1352             } else if (content instanceof byte[]) {
   1353                 length = ((byte[]) content).length;
   1354             } else {
   1355                 length = content.toString().length();
   1356             }
   1357         }
   1358 
   1359         try {
   1360             contentLengthHeader.setContentLength(length);
   1361         } catch (InvalidArgumentException e) {
   1362             // Cannot happen.
   1363         }
   1364     }
   1365 
   1366     /**
   1367      * Remove the message content if it exists.
   1368      */
   1369     public void removeContent() {
   1370         messageContent = null;
   1371         messageContentBytes = null;
   1372         messageContentObject = null;
   1373         try {
   1374             this.contentLengthHeader.setContentLength(0);
   1375         } catch (InvalidArgumentException ex) {
   1376         }
   1377     }
   1378 
   1379     /**
   1380      * Get a SIP header or Header list given its name.
   1381      *
   1382      * @param headerName is the name of the header to get.
   1383      * @return a header or header list that contians the retrieved header.
   1384      */
   1385     @SuppressWarnings("unchecked")
   1386     public ListIterator<SIPHeader> getHeaders(String headerName) {
   1387         if (headerName == null)
   1388             throw new NullPointerException("null headerName");
   1389         SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache
   1390                 .toLowerCase(headerName));
   1391         // empty iterator
   1392         if (sipHeader == null)
   1393             return new LinkedList<SIPHeader>().listIterator();
   1394         if (sipHeader instanceof SIPHeaderList) {
   1395             return ((SIPHeaderList<SIPHeader>) sipHeader).listIterator();
   1396         } else {
   1397             return new HeaderIterator(this, sipHeader);
   1398         }
   1399     }
   1400 
   1401     /**
   1402      * Get a header of the given name as a string. This concatenates the headers of a given type
   1403      * as a comma separted list. This is useful for formatting and printing headers.
   1404      *
   1405      * @param name
   1406      * @return the header as a formatted string
   1407      */
   1408     public String getHeaderAsFormattedString(String name) {
   1409         String lowerCaseName = name.toLowerCase();
   1410         if (this.nameTable.containsKey(lowerCaseName)) {
   1411             return this.nameTable.get(lowerCaseName).toString();
   1412         } else {
   1413             return this.getHeader(name).toString();
   1414         }
   1415     }
   1416 
   1417     private SIPHeader getSIPHeaderListLowerCase(String lowerCaseHeaderName) {
   1418         return nameTable.get(lowerCaseHeaderName);
   1419     }
   1420 
   1421     /**
   1422      * Get a list of headers of the given name ( or null if no such header exists ).
   1423      *
   1424      * @param headerName -- a header name from which to retrieve the list.
   1425      * @return -- a list of headers with that name.
   1426      */
   1427     @SuppressWarnings("unchecked")
   1428     private List<SIPHeader> getHeaderList(String headerName) {
   1429         SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache
   1430                 .toLowerCase(headerName));
   1431         if (sipHeader == null)
   1432             return null;
   1433         else if (sipHeader instanceof SIPHeaderList)
   1434             return (List<SIPHeader>) (((SIPHeaderList< ? >) sipHeader).getHeaderList());
   1435         else {
   1436             LinkedList<SIPHeader> ll = new LinkedList<SIPHeader>();
   1437             ll.add(sipHeader);
   1438             return ll;
   1439         }
   1440     }
   1441 
   1442     /**
   1443      * Return true if the SIPMessage has a header of the given name.
   1444      *
   1445      * @param headerName is the header name for which we are testing.
   1446      * @return true if the header is present in the message
   1447      */
   1448     public boolean hasHeader(String headerName) {
   1449         return nameTable.containsKey(SIPHeaderNamesCache.toLowerCase(headerName));
   1450     }
   1451 
   1452     /**
   1453      * Return true if the message has a From header tag.
   1454      *
   1455      * @return true if the message has a from header and that header has a tag.
   1456      */
   1457     public boolean hasFromTag() {
   1458         return fromHeader != null && fromHeader.getTag() != null;
   1459     }
   1460 
   1461     /**
   1462      * Return true if the message has a To header tag.
   1463      *
   1464      * @return true if the message has a to header and that header has a tag.
   1465      */
   1466     public boolean hasToTag() {
   1467         return toHeader != null && toHeader.getTag() != null;
   1468     }
   1469 
   1470     /**
   1471      * Return the from tag.
   1472      *
   1473      * @return the tag from the from header.
   1474      *
   1475      */
   1476     public String getFromTag() {
   1477         return fromHeader == null ? null : fromHeader.getTag();
   1478     }
   1479 
   1480     /**
   1481      * Set the From Tag.
   1482      *
   1483      * @param tag -- tag to set in the from header.
   1484      */
   1485     public void setFromTag(String tag) {
   1486         try {
   1487             fromHeader.setTag(tag);
   1488         } catch (ParseException e) {
   1489         }
   1490     }
   1491 
   1492     /**
   1493      * Set the to tag.
   1494      *
   1495      * @param tag -- tag to set.
   1496      */
   1497     public void setToTag(String tag) {
   1498         try {
   1499             toHeader.setTag(tag);
   1500         } catch (ParseException e) {
   1501         }
   1502     }
   1503 
   1504     /**
   1505      * Return the to tag.
   1506      */
   1507     public String getToTag() {
   1508         return toHeader == null ? null : toHeader.getTag();
   1509     }
   1510 
   1511     /**
   1512      * Return the encoded first line.
   1513      */
   1514     public abstract String getFirstLine();
   1515 
   1516     /**
   1517      * Add a SIP header.
   1518      *
   1519      * @param sipHeader -- sip header to add.
   1520      */
   1521     public void addHeader(Header sipHeader) {
   1522         // Content length is never stored. Just computed.
   1523         SIPHeader sh = (SIPHeader) sipHeader;
   1524         try {
   1525             if ((sipHeader instanceof ViaHeader) || (sipHeader instanceof RecordRouteHeader)) {
   1526                 attachHeader(sh, false, true);
   1527             } else {
   1528                 attachHeader(sh, false, false);
   1529             }
   1530         } catch (SIPDuplicateHeaderException ex) {
   1531             try {
   1532                 if (sipHeader instanceof ContentLength) {
   1533                     ContentLength cl = (ContentLength) sipHeader;
   1534                     contentLengthHeader.setContentLength(cl.getContentLength());
   1535                 }
   1536             } catch (InvalidArgumentException e) {
   1537             }
   1538         }
   1539     }
   1540 
   1541     /**
   1542      * Add a header to the unparsed list of headers.
   1543      *
   1544      * @param unparsed -- unparsed header to add to the list.
   1545      */
   1546     public void addUnparsed(String unparsed) {
   1547         this.unrecognizedHeaders.add(unparsed);
   1548     }
   1549 
   1550     /**
   1551      * Add a SIP header.
   1552      *
   1553      * @param sipHeader -- string version of SIP header to add.
   1554      */
   1555 
   1556     public void addHeader(String sipHeader) {
   1557         String hdrString = sipHeader.trim() + "\n";
   1558         try {
   1559             HeaderParser parser = ParserFactory.createParser(sipHeader);
   1560             SIPHeader sh = parser.parse();
   1561             this.attachHeader(sh, false);
   1562         } catch (ParseException ex) {
   1563             this.unrecognizedHeaders.add(hdrString);
   1564         }
   1565     }
   1566 
   1567     /**
   1568      * Get a list containing the unrecognized headers.
   1569      *
   1570      * @return a linked list containing unrecongnized headers.
   1571      */
   1572     public ListIterator<String> getUnrecognizedHeaders() {
   1573         return this.unrecognizedHeaders.listIterator();
   1574     }
   1575 
   1576     /**
   1577      * Get the header names.
   1578      *
   1579      * @return a list iterator to a list of header names. These are ordered in the same order as
   1580      *         are present in the message.
   1581      */
   1582     public ListIterator<String> getHeaderNames() {
   1583         Iterator<SIPHeader> li = this.headers.iterator();
   1584         LinkedList<String> retval = new LinkedList<String>();
   1585         while (li.hasNext()) {
   1586             SIPHeader sipHeader = (SIPHeader) li.next();
   1587             String name = sipHeader.getName();
   1588             retval.add(name);
   1589         }
   1590         return retval.listIterator();
   1591     }
   1592 
   1593     /**
   1594      * Compare for equality.
   1595      *
   1596      * @param other -- the other object to compare with.
   1597      */
   1598     public boolean equals(Object other) {
   1599         if (!other.getClass().equals(this.getClass())) {
   1600             return false;
   1601         }
   1602         SIPMessage otherMessage = (SIPMessage) other;
   1603         Collection<SIPHeader> values = this.nameTable.values();
   1604         Iterator<SIPHeader> it = values.iterator();
   1605         if (nameTable.size() != otherMessage.nameTable.size()) {
   1606             return false;
   1607         }
   1608 
   1609         while (it.hasNext()) {
   1610             SIPHeader mine = (SIPHeader) it.next();
   1611             SIPHeader his = (SIPHeader) (otherMessage.nameTable.get(SIPHeaderNamesCache
   1612                     .toLowerCase(mine.getName())));
   1613             if (his == null) {
   1614                 return false;
   1615             } else if (!his.equals(mine)) {
   1616                 return false;
   1617             }
   1618         }
   1619         return true;
   1620     }
   1621 
   1622     /**
   1623      * get content disposition header or null if no such header exists.
   1624      *
   1625      * @return the contentDisposition header
   1626      */
   1627     public javax.sip.header.ContentDispositionHeader getContentDisposition() {
   1628         return (ContentDispositionHeader) getHeaderLowerCase(CONTENT_DISPOSITION_LOWERCASE);
   1629     }
   1630 
   1631     private static final String CONTENT_DISPOSITION_LOWERCASE = SIPHeaderNamesCache
   1632             .toLowerCase(ContentDispositionHeader.NAME);
   1633 
   1634     /**
   1635      * get the content encoding header.
   1636      *
   1637      * @return the contentEncoding header.
   1638      */
   1639     public javax.sip.header.ContentEncodingHeader getContentEncoding() {
   1640         return (ContentEncodingHeader) getHeaderLowerCase(CONTENT_ENCODING_LOWERCASE);
   1641     }
   1642 
   1643     private static final String CONTENT_ENCODING_LOWERCASE = SIPHeaderNamesCache
   1644             .toLowerCase(ContentEncodingHeader.NAME);
   1645 
   1646     /**
   1647      * Get the contentLanguage header.
   1648      *
   1649      * @return the content language header.
   1650      */
   1651     public javax.sip.header.ContentLanguageHeader getContentLanguage() {
   1652         return (ContentLanguageHeader) getHeaderLowerCase(CONTENT_LANGUAGE_LOWERCASE);
   1653     }
   1654 
   1655     private static final String CONTENT_LANGUAGE_LOWERCASE = SIPHeaderNamesCache
   1656             .toLowerCase(ContentLanguageHeader.NAME);
   1657 
   1658     /**
   1659      * Get the exipres header.
   1660      *
   1661      * @return the expires header or null if one does not exist.
   1662      */
   1663     public javax.sip.header.ExpiresHeader getExpires() {
   1664         return (ExpiresHeader) getHeaderLowerCase(EXPIRES_LOWERCASE);
   1665     }
   1666 
   1667     private static final String EXPIRES_LOWERCASE = SIPHeaderNamesCache
   1668             .toLowerCase(ExpiresHeader.NAME);
   1669 
   1670     /**
   1671      * Set the expiresHeader
   1672      *
   1673      * @param expiresHeader -- the expires header to set.
   1674      */
   1675 
   1676     public void setExpires(ExpiresHeader expiresHeader) {
   1677         this.setHeader(expiresHeader);
   1678     }
   1679 
   1680     /**
   1681      * Set the content disposition header.
   1682      *
   1683      * @param contentDispositionHeader -- content disposition header.
   1684      */
   1685 
   1686     public void setContentDisposition(ContentDispositionHeader contentDispositionHeader) {
   1687         this.setHeader(contentDispositionHeader);
   1688 
   1689     }
   1690 
   1691     public void setContentEncoding(ContentEncodingHeader contentEncodingHeader) {
   1692         this.setHeader(contentEncodingHeader);
   1693 
   1694     }
   1695 
   1696     public void setContentLanguage(ContentLanguageHeader contentLanguageHeader) {
   1697         this.setHeader(contentLanguageHeader);
   1698     }
   1699 
   1700     /**
   1701      * Set the content length header.
   1702      *
   1703      * @param contentLength -- content length header.
   1704      */
   1705     public void setContentLength(ContentLengthHeader contentLength) {
   1706         try {
   1707             this.contentLengthHeader.setContentLength(contentLength.getContentLength());
   1708         } catch (InvalidArgumentException ex) {
   1709         }
   1710 
   1711     }
   1712 
   1713     /**
   1714      * Set the size of all the headers. This is for book keeping. Called by the parser.
   1715      *
   1716      * @param size -- size of the headers.
   1717      */
   1718     public void setSize(int size) {
   1719         this.size = size;
   1720     }
   1721 
   1722     public int getSize() {
   1723         return this.size;
   1724     }
   1725 
   1726     /*
   1727      * (non-Javadoc)
   1728      *
   1729      * @see javax.sip.message.Message#addLast(javax.sip.header.Header)
   1730      */
   1731     public void addLast(Header header) throws SipException, NullPointerException {
   1732         if (header == null)
   1733             throw new NullPointerException("null arg!");
   1734 
   1735         try {
   1736             this.attachHeader((SIPHeader) header, false, false);
   1737         } catch (SIPDuplicateHeaderException ex) {
   1738             throw new SipException("Cannot add header - header already exists");
   1739         }
   1740 
   1741     }
   1742 
   1743     /*
   1744      * (non-Javadoc)
   1745      *
   1746      * @see javax.sip.message.Message#addFirst(javax.sip.header.Header)
   1747      */
   1748     public void addFirst(Header header) throws SipException, NullPointerException {
   1749 
   1750         if (header == null)
   1751             throw new NullPointerException("null arg!");
   1752 
   1753         try {
   1754             this.attachHeader((SIPHeader) header, false, true);
   1755         } catch (SIPDuplicateHeaderException ex) {
   1756             throw new SipException("Cannot add header - header already exists");
   1757         }
   1758 
   1759     }
   1760 
   1761     /*
   1762      * (non-Javadoc)
   1763      *
   1764      * @see javax.sip.message.Message#removeFirst(java.lang.String)
   1765      */
   1766     public void removeFirst(String headerName) throws NullPointerException {
   1767         if (headerName == null)
   1768             throw new NullPointerException("Null argument Provided!");
   1769         this.removeHeader(headerName, true);
   1770 
   1771     }
   1772 
   1773     /*
   1774      * (non-Javadoc)
   1775      *
   1776      * @see javax.sip.message.Message#removeLast(java.lang.String)
   1777      */
   1778     public void removeLast(String headerName) {
   1779         if (headerName == null)
   1780             throw new NullPointerException("Null argument Provided!");
   1781         this.removeHeader(headerName, false);
   1782 
   1783     }
   1784 
   1785     /**
   1786      * Set the CSeq header.
   1787      *
   1788      * @param cseqHeader -- CSeq Header.
   1789      */
   1790 
   1791     public void setCSeq(CSeqHeader cseqHeader) {
   1792         this.setHeader(cseqHeader);
   1793     }
   1794 
   1795     /**
   1796      * Set the application data pointer. This method is not used the stack. It is provided as a
   1797      * convenient way of storing book-keeping data for applications. Note that null clears the
   1798      * application data pointer (releases it).
   1799      *
   1800      * @param applicationData -- application data pointer to set. null clears the application data
   1801      *        pointer.
   1802      */
   1803     public void setApplicationData(Object applicationData) {
   1804         this.applicationData = applicationData;
   1805     }
   1806 
   1807     /**
   1808      * Get the application data associated with this message.
   1809      *
   1810      * @return stored application data.
   1811      */
   1812     public Object getApplicationData() {
   1813         return this.applicationData;
   1814     }
   1815 
   1816     /**
   1817      * Get the multipart MIME content
   1818      *
   1819      */
   1820     public MultipartMimeContent getMultipartMimeContent() throws ParseException {
   1821         if (this.contentLengthHeader.getContentLength() == 0) {
   1822             return null;
   1823         }
   1824         MultipartMimeContentImpl retval = new MultipartMimeContentImpl(this
   1825                 .getContentTypeHeader());
   1826         byte[] rawContent = getRawContent();
   1827 		try {
   1828 			String body = new String( rawContent, getCharset() );
   1829 	        retval.createContentList(body);
   1830 	        return retval;
   1831 		} catch (UnsupportedEncodingException e) {
   1832 			InternalErrorHandler.handleException(e);
   1833 			return null;
   1834 		}
   1835     }
   1836 
   1837     public CallIdHeader getCallIdHeader() {
   1838         return this.callIdHeader;
   1839     }
   1840 
   1841 
   1842     public FromHeader getFromHeader() {
   1843         return this.fromHeader;
   1844     }
   1845 
   1846 
   1847     public ToHeader getToHeader() {
   1848         return this.toHeader;
   1849     }
   1850 
   1851 
   1852     public ViaHeader getTopmostViaHeader() {
   1853         return this.getTopmostVia();
   1854     }
   1855 
   1856     public CSeqHeader getCSeqHeader() {
   1857         return this.cSeqHeader;
   1858     }
   1859 
   1860     /**
   1861      * Returns the charset to use for encoding/decoding the body of this message
   1862      */
   1863     protected final String getCharset() {
   1864     	ContentType ct = getContentTypeHeader();
   1865     	if (ct!=null) {
   1866     		String c = ct.getCharset();
   1867     		return c!=null ? c : contentEncodingCharset;
   1868     	} else return contentEncodingCharset;
   1869     }
   1870 
   1871     /**
   1872      * Return true if this is a null request (i.e. does not have a request line ).
   1873      *
   1874      * @return true if null request.
   1875      */
   1876     public boolean isNullRequest() {
   1877         return  this.nullRequest;
   1878     }
   1879 
   1880     /**
   1881      * Set a flag to indiate this is a special message ( encoded with CRLFCRLF ).
   1882      *
   1883      */
   1884     public void setNullRequest() {
   1885         this.nullRequest = true;
   1886     }
   1887 
   1888 
   1889     public abstract void setSIPVersion(String sipVersion) throws ParseException;
   1890 
   1891     public abstract String getSIPVersion();
   1892 
   1893     public abstract String toString();
   1894 
   1895 }
   1896