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.SIPConstants;
33import gov.nist.javax.sip.Utils;
34import gov.nist.javax.sip.header.AlertInfo;
35import gov.nist.javax.sip.header.Authorization;
36import gov.nist.javax.sip.header.CSeq;
37import gov.nist.javax.sip.header.CallID;
38import gov.nist.javax.sip.header.Contact;
39import gov.nist.javax.sip.header.ContactList;
40import gov.nist.javax.sip.header.ContentLength;
41import gov.nist.javax.sip.header.ContentType;
42import gov.nist.javax.sip.header.ErrorInfo;
43import gov.nist.javax.sip.header.ErrorInfoList;
44import gov.nist.javax.sip.header.From;
45import gov.nist.javax.sip.header.InReplyTo;
46import gov.nist.javax.sip.header.MaxForwards;
47import gov.nist.javax.sip.header.Priority;
48import gov.nist.javax.sip.header.ProxyAuthenticate;
49import gov.nist.javax.sip.header.ProxyAuthorization;
50import gov.nist.javax.sip.header.ProxyRequire;
51import gov.nist.javax.sip.header.ProxyRequireList;
52import gov.nist.javax.sip.header.RSeq;
53import gov.nist.javax.sip.header.RecordRouteList;
54import gov.nist.javax.sip.header.RetryAfter;
55import gov.nist.javax.sip.header.Route;
56import gov.nist.javax.sip.header.RouteList;
57import gov.nist.javax.sip.header.SIPETag;
58import gov.nist.javax.sip.header.SIPHeader;
59import gov.nist.javax.sip.header.SIPHeaderList;
60import gov.nist.javax.sip.header.SIPHeaderNamesCache;
61import gov.nist.javax.sip.header.SIPIfMatch;
62import gov.nist.javax.sip.header.Server;
63import gov.nist.javax.sip.header.Subject;
64import gov.nist.javax.sip.header.To;
65import gov.nist.javax.sip.header.Unsupported;
66import gov.nist.javax.sip.header.UserAgent;
67import gov.nist.javax.sip.header.Via;
68import gov.nist.javax.sip.header.ViaList;
69import gov.nist.javax.sip.header.WWWAuthenticate;
70import gov.nist.javax.sip.header.Warning;
71import gov.nist.javax.sip.parser.HeaderParser;
72import gov.nist.javax.sip.parser.ParserFactory;
73import gov.nist.javax.sip.parser.PipelinedMsgParser;
74import gov.nist.javax.sip.parser.StringMsgParser;
75
76import java.io.UnsupportedEncodingException;
77import java.lang.reflect.Field;
78import java.text.ParseException;
79import java.util.Collection;
80import java.util.Hashtable;
81import java.util.Iterator;
82import java.util.LinkedList;
83import java.util.List;
84import java.util.ListIterator;
85import java.util.concurrent.ConcurrentLinkedQueue;
86
87import javax.sip.InvalidArgumentException;
88import javax.sip.SipException;
89import javax.sip.header.AuthorizationHeader;
90import javax.sip.header.CSeqHeader;
91import javax.sip.header.CallIdHeader;
92import javax.sip.header.ContactHeader;
93import javax.sip.header.ContentDispositionHeader;
94import javax.sip.header.ContentEncodingHeader;
95import javax.sip.header.ContentLanguageHeader;
96import javax.sip.header.ContentLengthHeader;
97import javax.sip.header.ContentTypeHeader;
98import javax.sip.header.ExpiresHeader;
99import javax.sip.header.FromHeader;
100import javax.sip.header.Header;
101import javax.sip.header.MaxForwardsHeader;
102import javax.sip.header.RecordRouteHeader;
103import javax.sip.header.RouteHeader;
104import javax.sip.header.ToHeader;
105import javax.sip.header.ViaHeader;
106import 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 */
126public 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