| /* |
| * Conditions Of Use |
| * |
| * This software was developed by employees of the National Institute of |
| * Standards and Technology (NIST), an agency of the Federal Government. |
| * Pursuant to title 15 Untied States Code Section 105, works of NIST |
| * employees are not subject to copyright protection in the United States |
| * and are considered to be in the public domain. As a result, a formal |
| * license is not needed to use the software. |
| * |
| * This software is provided by NIST as a service and is expressly |
| * provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED |
| * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT |
| * AND DATA ACCURACY. NIST does not warrant or make any representations |
| * regarding the use of the software or the results thereof, including but |
| * not limited to the correctness, accuracy, reliability or usefulness of |
| * the software. |
| * |
| * Permission to use this software is contingent upon your acceptance |
| * of the terms of this agreement |
| * |
| * . |
| * |
| */ |
| /******************************************************************************* |
| * Product of NIST/ITL Advanced Networking Technologies Division (ANTD) * |
| ******************************************************************************/ |
| package gov.nist.javax.sip.message; |
| |
| import gov.nist.core.InternalErrorHandler; |
| import gov.nist.javax.sip.SIPConstants; |
| import gov.nist.javax.sip.Utils; |
| import gov.nist.javax.sip.header.AlertInfo; |
| import gov.nist.javax.sip.header.Authorization; |
| import gov.nist.javax.sip.header.CSeq; |
| import gov.nist.javax.sip.header.CallID; |
| import gov.nist.javax.sip.header.Contact; |
| import gov.nist.javax.sip.header.ContactList; |
| import gov.nist.javax.sip.header.ContentLength; |
| import gov.nist.javax.sip.header.ContentType; |
| import gov.nist.javax.sip.header.ErrorInfo; |
| import gov.nist.javax.sip.header.ErrorInfoList; |
| import gov.nist.javax.sip.header.From; |
| import gov.nist.javax.sip.header.InReplyTo; |
| import gov.nist.javax.sip.header.MaxForwards; |
| import gov.nist.javax.sip.header.Priority; |
| import gov.nist.javax.sip.header.ProxyAuthenticate; |
| import gov.nist.javax.sip.header.ProxyAuthorization; |
| import gov.nist.javax.sip.header.ProxyRequire; |
| import gov.nist.javax.sip.header.ProxyRequireList; |
| import gov.nist.javax.sip.header.RSeq; |
| import gov.nist.javax.sip.header.RecordRouteList; |
| import gov.nist.javax.sip.header.RetryAfter; |
| import gov.nist.javax.sip.header.Route; |
| import gov.nist.javax.sip.header.RouteList; |
| import gov.nist.javax.sip.header.SIPETag; |
| import gov.nist.javax.sip.header.SIPHeader; |
| import gov.nist.javax.sip.header.SIPHeaderList; |
| import gov.nist.javax.sip.header.SIPHeaderNamesCache; |
| import gov.nist.javax.sip.header.SIPIfMatch; |
| import gov.nist.javax.sip.header.Server; |
| import gov.nist.javax.sip.header.Subject; |
| import gov.nist.javax.sip.header.To; |
| import gov.nist.javax.sip.header.Unsupported; |
| import gov.nist.javax.sip.header.UserAgent; |
| import gov.nist.javax.sip.header.Via; |
| import gov.nist.javax.sip.header.ViaList; |
| import gov.nist.javax.sip.header.WWWAuthenticate; |
| import gov.nist.javax.sip.header.Warning; |
| import gov.nist.javax.sip.parser.HeaderParser; |
| import gov.nist.javax.sip.parser.ParserFactory; |
| import gov.nist.javax.sip.parser.PipelinedMsgParser; |
| import gov.nist.javax.sip.parser.StringMsgParser; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.lang.reflect.Field; |
| import java.text.ParseException; |
| import java.util.Collection; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| |
| import javax.sip.InvalidArgumentException; |
| import javax.sip.SipException; |
| import javax.sip.header.AuthorizationHeader; |
| import javax.sip.header.CSeqHeader; |
| import javax.sip.header.CallIdHeader; |
| import javax.sip.header.ContactHeader; |
| import javax.sip.header.ContentDispositionHeader; |
| import javax.sip.header.ContentEncodingHeader; |
| import javax.sip.header.ContentLanguageHeader; |
| import javax.sip.header.ContentLengthHeader; |
| import javax.sip.header.ContentTypeHeader; |
| import javax.sip.header.ExpiresHeader; |
| import javax.sip.header.FromHeader; |
| import javax.sip.header.Header; |
| import javax.sip.header.MaxForwardsHeader; |
| import javax.sip.header.RecordRouteHeader; |
| import javax.sip.header.RouteHeader; |
| import javax.sip.header.ToHeader; |
| import javax.sip.header.ViaHeader; |
| import javax.sip.message.Request; |
| |
| /* |
| * Acknowledgements: Yanick Belanger sent in a patch for the right content length when the content |
| * is a String. Bill Mccormick from Nortel Networks sent in a bug fix for setContent. |
| * |
| */ |
| /** |
| * This is the main SIP Message structure. |
| * |
| * @see StringMsgParser |
| * @see PipelinedMsgParser |
| * |
| * @version 1.2 $Revision: 1.53 $ $Date: 2009/12/16 14:58:40 $ |
| * @since 1.1 |
| * |
| * @author M. Ranganathan <br/> |
| * |
| * |
| */ |
| public abstract class SIPMessage extends MessageObject implements javax.sip.message.Message, |
| MessageExt { |
| |
| // JvB: use static here? |
| private String contentEncodingCharset = MessageFactoryImpl.getDefaultContentEncodingCharset(); |
| |
| /* |
| * True if this is a null request. |
| */ |
| protected boolean nullRequest; |
| |
| /** |
| * unparsed headers |
| */ |
| protected LinkedList<String> unrecognizedHeaders; |
| |
| /** |
| * List of parsed headers (in the order they were added) |
| */ |
| protected ConcurrentLinkedQueue<SIPHeader> headers; |
| |
| /** |
| * Direct accessors for frequently accessed headers |
| */ |
| protected From fromHeader; |
| |
| protected To toHeader; |
| |
| protected CSeq cSeqHeader; |
| |
| protected CallID callIdHeader; |
| |
| protected ContentLength contentLengthHeader; |
| |
| protected MaxForwards maxForwardsHeader; |
| |
| // Cumulative size of all the headers. |
| protected int size; |
| |
| // Payload |
| private String messageContent; |
| |
| private byte[] messageContentBytes; |
| |
| private Object messageContentObject; |
| |
| // Table of headers indexed by name. |
| private Hashtable<String, SIPHeader> nameTable; |
| |
| /** |
| * The application data pointer. This is un-interpreted by the stack. This is provided as a |
| * convenient way of keeping book-keeping data for applications. |
| */ |
| protected Object applicationData; |
| |
| /** |
| * Return true if the header belongs only in a Request. |
| * |
| * @param sipHeader is the header to test. |
| */ |
| public static boolean isRequestHeader(SIPHeader sipHeader) { |
| return sipHeader instanceof AlertInfo || sipHeader instanceof InReplyTo |
| || sipHeader instanceof Authorization || sipHeader instanceof MaxForwards |
| || sipHeader instanceof UserAgent || sipHeader instanceof Priority |
| || sipHeader instanceof ProxyAuthorization || sipHeader instanceof ProxyRequire |
| || sipHeader instanceof ProxyRequireList || sipHeader instanceof Route |
| || sipHeader instanceof RouteList || sipHeader instanceof Subject |
| || sipHeader instanceof SIPIfMatch; |
| } |
| |
| /** |
| * Return true if the header belongs only in a response. |
| * |
| * @param sipHeader is the header to test. |
| */ |
| public static boolean isResponseHeader(SIPHeader sipHeader) { |
| return sipHeader instanceof ErrorInfo || sipHeader instanceof ProxyAuthenticate |
| || sipHeader instanceof Server || sipHeader instanceof Unsupported |
| || sipHeader instanceof RetryAfter || sipHeader instanceof Warning |
| || sipHeader instanceof WWWAuthenticate || sipHeader instanceof SIPETag |
| || sipHeader instanceof RSeq; |
| |
| } |
| |
| /** |
| * Get the headers as a linked list of encoded Strings |
| * |
| * @return a linked list with each element of the list containing a string encoded header in |
| * canonical form. |
| */ |
| public LinkedList<String> getMessageAsEncodedStrings() { |
| LinkedList<String> retval = new LinkedList<String>(); |
| Iterator<SIPHeader> li = headers.iterator(); |
| while (li.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) li.next(); |
| if (sipHeader instanceof SIPHeaderList) { |
| SIPHeaderList< ? > shl = (SIPHeaderList< ? >) sipHeader; |
| retval.addAll(shl.getHeadersAsEncodedStrings()); |
| } else { |
| retval.add(sipHeader.encode()); |
| } |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * Encode only the message and exclude the contents (for debugging); |
| * |
| * @return a string with all the headers encoded. |
| */ |
| protected String encodeSIPHeaders() { |
| StringBuffer encoding = new StringBuffer(); |
| Iterator<SIPHeader> it = this.headers.iterator(); |
| |
| while (it.hasNext()) { |
| SIPHeader siphdr = (SIPHeader) it.next(); |
| if (!(siphdr instanceof ContentLength)) |
| siphdr.encode(encoding); |
| } |
| |
| return contentLengthHeader.encode(encoding).append(NEWLINE).toString(); |
| } |
| |
| /** |
| * Encode all the headers except the contents. For debug logging. |
| */ |
| public abstract String encodeMessage(); |
| |
| /** |
| * Get A dialog identifier constructed from this messsage. This is an id that can be used to |
| * identify dialogs. |
| * |
| * @param isServerTransaction is a flag that indicates whether this is a server transaction. |
| */ |
| public abstract String getDialogId(boolean isServerTransaction); |
| |
| /** |
| * Template match for SIP messages. The matchObj is a SIPMessage template to match against. |
| * This method allows you to do pattern matching with incoming SIP messages. Null matches wild |
| * card. |
| * |
| * @param other is the match template to match against. |
| * @return true if a match occured and false otherwise. |
| */ |
| public boolean match(Object other) { |
| if (other == null) |
| return true; |
| if (!other.getClass().equals(this.getClass())) |
| return false; |
| SIPMessage matchObj = (SIPMessage) other; |
| Iterator<SIPHeader> li = matchObj.getHeaders(); |
| while (li.hasNext()) { |
| SIPHeader hisHeaders = (SIPHeader) li.next(); |
| List<SIPHeader> myHeaders = this.getHeaderList(hisHeaders.getHeaderName()); |
| |
| // Could not find a header to match his header. |
| if (myHeaders == null || myHeaders.size() == 0) |
| return false; |
| |
| if (hisHeaders instanceof SIPHeaderList) { |
| ListIterator< ? > outerIterator = ((SIPHeaderList< ? >) hisHeaders) |
| .listIterator(); |
| while (outerIterator.hasNext()) { |
| SIPHeader hisHeader = (SIPHeader) outerIterator.next(); |
| if (hisHeader instanceof ContentLength) |
| continue; |
| ListIterator< ? > innerIterator = myHeaders.listIterator(); |
| boolean found = false; |
| while (innerIterator.hasNext()) { |
| SIPHeader myHeader = (SIPHeader) innerIterator.next(); |
| if (myHeader.match(hisHeader)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) |
| return false; |
| } |
| } else { |
| SIPHeader hisHeader = hisHeaders; |
| ListIterator<SIPHeader> innerIterator = myHeaders.listIterator(); |
| boolean found = false; |
| while (innerIterator.hasNext()) { |
| SIPHeader myHeader = (SIPHeader) innerIterator.next(); |
| if (myHeader.match(hisHeader)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) |
| return false; |
| } |
| } |
| return true; |
| |
| } |
| |
| /** |
| * Merge a request with a template |
| * |
| * @param template -- template to merge with. |
| * |
| */ |
| public void merge(Object template) { |
| if (!template.getClass().equals(this.getClass())) |
| throw new IllegalArgumentException("Bad class " + template.getClass()); |
| SIPMessage templateMessage = (SIPMessage) template; |
| Object[] templateHeaders = templateMessage.headers.toArray(); |
| for (int i = 0; i < templateHeaders.length; i++) { |
| SIPHeader hdr = (SIPHeader) templateHeaders[i]; |
| String hdrName = hdr.getHeaderName(); |
| List<SIPHeader> myHdrs = this.getHeaderList(hdrName); |
| if (myHdrs == null) { |
| this.attachHeader(hdr); |
| } else { |
| ListIterator<SIPHeader> it = myHdrs.listIterator(); |
| while (it.hasNext()) { |
| SIPHeader sipHdr = (SIPHeader) it.next(); |
| sipHdr.merge(hdr); |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Encode this message as a string. This is more efficient when the payload is a string |
| * (rather than a binary array of bytes). If the payload cannot be encoded as a UTF-8 string |
| * then it is simply ignored (will not appear in the encoded message). |
| * |
| * @return The Canonical String representation of the message (including the canonical string |
| * representation of the SDP payload if it exists). |
| */ |
| public String encode() { |
| StringBuffer encoding = new StringBuffer(); |
| Iterator<SIPHeader> it = this.headers.iterator(); |
| |
| while (it.hasNext()) { |
| SIPHeader siphdr = (SIPHeader) it.next(); |
| if (!(siphdr instanceof ContentLength)) |
| encoding.append(siphdr.encode()); |
| } |
| // Append the unrecognized headers. Headers that are not |
| // recognized are passed through unchanged. |
| for (String unrecognized : this.unrecognizedHeaders) { |
| encoding.append(unrecognized).append(NEWLINE); |
| } |
| |
| encoding.append(contentLengthHeader.encode()).append(NEWLINE); |
| |
| if (this.messageContentObject != null) { |
| String mbody = this.getContent().toString(); |
| |
| encoding.append(mbody); |
| } else if (this.messageContent != null || this.messageContentBytes != null) { |
| |
| String content = null; |
| try { |
| if (messageContent != null) |
| content = messageContent; |
| else { |
| // JvB: Check for 'charset' parameter which overrides the default UTF-8 |
| content = new String(messageContentBytes, getCharset() ); |
| } |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| |
| encoding.append(content); |
| } |
| return encoding.toString(); |
| } |
| |
| /** |
| * Encode the message as a byte array. Use this when the message payload is a binary byte |
| * array. |
| * |
| * @return The Canonical byte array representation of the message (including the canonical |
| * byte array representation of the SDP payload if it exists all in one contiguous |
| * byte array). |
| */ |
| public byte[] encodeAsBytes(String transport) { |
| if (this instanceof SIPRequest && ((SIPRequest) this).isNullRequest()) { |
| return "\r\n\r\n".getBytes(); |
| } |
| // JvB: added to fix case where application provides the wrong transport |
| // in the topmost Via header |
| ViaHeader topVia = (ViaHeader) this.getHeader(ViaHeader.NAME); |
| try { |
| topVia.setTransport(transport); |
| } catch (ParseException e) { |
| InternalErrorHandler.handleException(e); |
| } |
| |
| StringBuffer encoding = new StringBuffer(); |
| synchronized (this.headers) { |
| Iterator<SIPHeader> it = this.headers.iterator(); |
| |
| while (it.hasNext()) { |
| SIPHeader siphdr = (SIPHeader) it.next(); |
| if (!(siphdr instanceof ContentLength)) |
| siphdr.encode(encoding); |
| |
| } |
| } |
| contentLengthHeader.encode(encoding); |
| encoding.append(NEWLINE); |
| |
| byte[] retval = null; |
| byte[] content = this.getRawContent(); |
| if (content != null) { |
| // Append the content |
| |
| byte[] msgarray = null; |
| try { |
| msgarray = encoding.toString().getBytes( getCharset() ); |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| |
| retval = new byte[msgarray.length + content.length]; |
| System.arraycopy(msgarray, 0, retval, 0, msgarray.length); |
| System.arraycopy(content, 0, retval, msgarray.length, content.length); |
| } else { |
| // Message content does not exist. |
| |
| try { |
| retval = encoding.toString().getBytes( getCharset() ); |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| } |
| return retval; |
| } |
| |
| /** |
| * clone this message (create a new deep physical copy). All headers in the message are |
| * cloned. You can modify the cloned copy without affecting the original. The content is |
| * handled as follows: If the content is a String, or a byte array, a new copy of the content |
| * is allocated and copied over. If the content is an Object that supports the clone method, |
| * then the clone method is invoked and the cloned content is the new content. Otherwise, the |
| * content of the new message is set equal to the old one. |
| * |
| * @return A cloned copy of this object. |
| */ |
| public Object clone() { |
| SIPMessage retval = (SIPMessage) super.clone(); |
| retval.nameTable = new Hashtable<String, SIPHeader>(); |
| retval.fromHeader = null; |
| retval.toHeader = null; |
| retval.cSeqHeader = null; |
| retval.callIdHeader = null; |
| retval.contentLengthHeader = null; |
| retval.maxForwardsHeader = null; |
| if (this.headers != null) { |
| retval.headers = new ConcurrentLinkedQueue<SIPHeader>(); |
| for (Iterator<SIPHeader> iter = headers.iterator(); iter.hasNext();) { |
| SIPHeader hdr = (SIPHeader) iter.next(); |
| retval.attachHeader((SIPHeader) hdr.clone()); |
| } |
| |
| } |
| if (this.messageContentBytes != null) |
| retval.messageContentBytes = (byte[]) this.messageContentBytes.clone(); |
| if (this.messageContentObject != null) |
| retval.messageContentObject = makeClone(messageContentObject); |
| retval.unrecognizedHeaders = this.unrecognizedHeaders; |
| return retval; |
| } |
| |
| /** |
| * Get the string representation of this header (for pretty printing the generated structure). |
| * |
| * @return Formatted string representation of the object. Note that this is NOT the same as |
| * encode(). This is used mainly for debugging purposes. |
| */ |
| public String debugDump() { |
| stringRepresentation = ""; |
| sprint("SIPMessage:"); |
| sprint("{"); |
| try { |
| |
| Field[] fields = this.getClass().getDeclaredFields(); |
| for (int i = 0; i < fields.length; i++) { |
| Field f = fields[i]; |
| Class< ? > fieldType = f.getType(); |
| String fieldName = f.getName(); |
| if (f.get(this) != null && SIPHeader.class.isAssignableFrom(fieldType) |
| && fieldName.compareTo("headers") != 0) { |
| sprint(fieldName + "="); |
| sprint(((SIPHeader) f.get(this)).debugDump()); |
| } |
| } |
| } catch (Exception ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| |
| sprint("List of headers : "); |
| sprint(headers.toString()); |
| sprint("messageContent = "); |
| sprint("{"); |
| sprint(messageContent); |
| sprint("}"); |
| if (this.getContent() != null) { |
| sprint(this.getContent().toString()); |
| } |
| sprint("}"); |
| return stringRepresentation; |
| } |
| |
| /** |
| * Constructor: Initializes lists and list headers. All the headers for which there can be |
| * multiple occurances in a message are derived from the SIPHeaderListClass. All singleton |
| * headers are derived from SIPHeader class. |
| */ |
| public SIPMessage() { |
| this.unrecognizedHeaders = new LinkedList<String>(); |
| this.headers = new ConcurrentLinkedQueue<SIPHeader>(); |
| nameTable = new Hashtable<String, SIPHeader>(); |
| try { |
| this.attachHeader(new ContentLength(0), false); |
| } catch (Exception ex) { |
| } |
| } |
| |
| /** |
| * Attach a header and die if you get a duplicate header exception. |
| * |
| * @param h SIPHeader to attach. |
| */ |
| private void attachHeader(SIPHeader h) { |
| if (h == null) |
| throw new IllegalArgumentException("null header!"); |
| try { |
| if (h instanceof SIPHeaderList) { |
| SIPHeaderList< ? > hl = (SIPHeaderList< ? >) h; |
| if (hl.isEmpty()) { |
| return; |
| } |
| } |
| attachHeader(h, false, false); |
| } catch (SIPDuplicateHeaderException ex) { |
| // InternalErrorHandler.handleException(ex); |
| } |
| } |
| |
| /** |
| * Attach a header (replacing the original header). |
| * |
| * @param sipHeader SIPHeader that replaces a header of the same type. |
| */ |
| public void setHeader(Header sipHeader) { |
| SIPHeader header = (SIPHeader) sipHeader; |
| if (header == null) |
| throw new IllegalArgumentException("null header!"); |
| try { |
| if (header instanceof SIPHeaderList) { |
| SIPHeaderList< ? > hl = (SIPHeaderList< ? >) header; |
| // Ignore empty lists. |
| if (hl.isEmpty()) |
| return; |
| } |
| this.removeHeader(header.getHeaderName()); |
| attachHeader(header, true, false); |
| } catch (SIPDuplicateHeaderException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| } |
| |
| /** |
| * Set a header from a linked list of headers. |
| * |
| * @param headers -- a list of headers to set. |
| */ |
| public void setHeaders(java.util.List<SIPHeader> headers) { |
| ListIterator<SIPHeader> listIterator = headers.listIterator(); |
| while (listIterator.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) listIterator.next(); |
| try { |
| this.attachHeader(sipHeader, false); |
| } catch (SIPDuplicateHeaderException ex) { |
| } |
| } |
| } |
| |
| /** |
| * Attach a header to the end of the existing headers in this SIPMessage structure. This is |
| * equivalent to the attachHeader(SIPHeader,replaceflag,false); which is the normal way in |
| * which headers are attached. This was added in support of JAIN-SIP. |
| * |
| * @param h header to attach. |
| * @param replaceflag if true then replace a header if it exists. |
| * @throws SIPDuplicateHeaderException If replaceFlag is false and only a singleton header is |
| * allowed (fpr example CSeq). |
| */ |
| public void attachHeader(SIPHeader h, boolean replaceflag) throws SIPDuplicateHeaderException { |
| this.attachHeader(h, replaceflag, false); |
| } |
| |
| /** |
| * Attach the header to the SIP Message structure at a specified position in its list of |
| * headers. |
| * |
| * @param header Header to attach. |
| * @param replaceFlag If true then replace the existing header. |
| * @param top Location in the header list to insert the header. |
| * @exception SIPDuplicateHeaderException if the header is of a type that cannot tolerate |
| * duplicates and one of this type already exists (e.g. CSeq header). |
| * @throws IndexOutOfBoundsException If the index specified is greater than the number of |
| * headers that are in this message. |
| */ |
| |
| public void attachHeader(SIPHeader header, boolean replaceFlag, boolean top) |
| throws SIPDuplicateHeaderException { |
| if (header == null) { |
| throw new NullPointerException("null header"); |
| } |
| |
| SIPHeader h; |
| |
| if (ListMap.hasList(header) && !SIPHeaderList.class.isAssignableFrom(header.getClass())) { |
| SIPHeaderList<SIPHeader> hdrList = ListMap.getList(header); |
| hdrList.add(header); |
| h = hdrList; |
| } else { |
| h = header; |
| } |
| |
| String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(h.getName()); |
| if (replaceFlag) { |
| nameTable.remove(headerNameLowerCase); |
| } else if (nameTable.containsKey(headerNameLowerCase) && !(h instanceof SIPHeaderList)) { |
| if (h instanceof ContentLength) { |
| try { |
| ContentLength cl = (ContentLength) h; |
| contentLengthHeader.setContentLength(cl.getContentLength()); |
| } catch (InvalidArgumentException e) { |
| } |
| } |
| // Just ignore duplicate header. |
| return; |
| } |
| |
| SIPHeader originalHeader = (SIPHeader) getHeader(header.getName()); |
| |
| // Delete the original header from our list structure. |
| if (originalHeader != null) { |
| Iterator<SIPHeader> li = headers.iterator(); |
| while (li.hasNext()) { |
| SIPHeader next = (SIPHeader) li.next(); |
| if (next.equals(originalHeader)) { |
| li.remove(); |
| } |
| } |
| } |
| |
| if (!nameTable.containsKey(headerNameLowerCase)) { |
| nameTable.put(headerNameLowerCase, h); |
| headers.add(h); |
| } else { |
| if (h instanceof SIPHeaderList) { |
| SIPHeaderList< ? > hdrlist = (SIPHeaderList< ? >) nameTable |
| .get(headerNameLowerCase); |
| if (hdrlist != null) |
| hdrlist.concatenate((SIPHeaderList) h, top); |
| else |
| nameTable.put(headerNameLowerCase, h); |
| } else { |
| nameTable.put(headerNameLowerCase, h); |
| } |
| } |
| |
| // Direct accessor fields for frequently accessed headers. |
| if (h instanceof From) { |
| this.fromHeader = (From) h; |
| } else if (h instanceof ContentLength) { |
| this.contentLengthHeader = (ContentLength) h; |
| } else if (h instanceof To) { |
| this.toHeader = (To) h; |
| } else if (h instanceof CSeq) { |
| this.cSeqHeader = (CSeq) h; |
| } else if (h instanceof CallID) { |
| this.callIdHeader = (CallID) h; |
| } else if (h instanceof MaxForwards) { |
| this.maxForwardsHeader = (MaxForwards) h; |
| } |
| |
| } |
| |
| /** |
| * Remove a header given its name. If multiple headers of a given name are present then the |
| * top flag determines which end to remove headers from. |
| * |
| * @param headerName is the name of the header to remove. |
| * @param top -- flag that indicates which end of header list to process. |
| */ |
| public void removeHeader(String headerName, boolean top) { |
| |
| String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName); |
| SIPHeader toRemove = (SIPHeader) nameTable.get(headerNameLowerCase); |
| // nothing to do then we are done. |
| if (toRemove == null) |
| return; |
| if (toRemove instanceof SIPHeaderList) { |
| SIPHeaderList< ? > hdrList = (SIPHeaderList< ? >) toRemove; |
| if (top) |
| hdrList.removeFirst(); |
| else |
| hdrList.removeLast(); |
| // Clean up empty list |
| if (hdrList.isEmpty()) { |
| Iterator<SIPHeader> li = this.headers.iterator(); |
| while (li.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) li.next(); |
| if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase)) |
| li.remove(); |
| } |
| |
| // JvB: also remove it from the nameTable! Else NPE in |
| // DefaultRouter |
| nameTable.remove(headerNameLowerCase); |
| } |
| } else { |
| this.nameTable.remove(headerNameLowerCase); |
| if (toRemove instanceof From) { |
| this.fromHeader = null; |
| } else if (toRemove instanceof To) { |
| this.toHeader = null; |
| } else if (toRemove instanceof CSeq) { |
| this.cSeqHeader = null; |
| } else if (toRemove instanceof CallID) { |
| this.callIdHeader = null; |
| } else if (toRemove instanceof MaxForwards) { |
| this.maxForwardsHeader = null; |
| } else if (toRemove instanceof ContentLength) { |
| this.contentLengthHeader = null; |
| } |
| Iterator<SIPHeader> li = this.headers.iterator(); |
| while (li.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) li.next(); |
| if (sipHeader.getName().equalsIgnoreCase(headerName)) |
| li.remove(); |
| } |
| } |
| |
| } |
| |
| /** |
| * Remove all headers given its name. |
| * |
| * @param headerName is the name of the header to remove. |
| */ |
| public void removeHeader(String headerName) { |
| |
| if (headerName == null) |
| throw new NullPointerException("null arg"); |
| String headerNameLowerCase = SIPHeaderNamesCache.toLowerCase(headerName); |
| SIPHeader removed = (SIPHeader) nameTable.remove(headerNameLowerCase); |
| // nothing to do then we are done. |
| if (removed == null) |
| return; |
| |
| // Remove the fast accessor fields. |
| if (removed instanceof From) { |
| this.fromHeader = null; |
| } else if (removed instanceof To) { |
| this.toHeader = null; |
| } else if (removed instanceof CSeq) { |
| this.cSeqHeader = null; |
| } else if (removed instanceof CallID) { |
| this.callIdHeader = null; |
| } else if (removed instanceof MaxForwards) { |
| this.maxForwardsHeader = null; |
| } else if (removed instanceof ContentLength) { |
| this.contentLengthHeader = null; |
| } |
| |
| Iterator<SIPHeader> li = this.headers.iterator(); |
| while (li.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) li.next(); |
| if (sipHeader.getName().equalsIgnoreCase(headerNameLowerCase)) |
| li.remove(); |
| |
| } |
| } |
| |
| /** |
| * Generate (compute) a transaction ID for this SIP message. |
| * |
| * @return A string containing the concatenation of various portions of the From,To,Via and |
| * RequestURI portions of this message as specified in RFC 2543: All responses to a |
| * request contain the same values in the Call-ID, CSeq, To, and From fields (with the |
| * possible addition of a tag in the To field (section 10.43)). This allows responses |
| * to be matched with requests. Incorporates a bug fix for a bug sent in by Gordon |
| * Ledgard of IPera for generating transactionIDs when no port is present in the via |
| * header. Incorporates a bug fix for a bug report sent in by Chris Mills of Nortel |
| * Networks (converts to lower case when returning the transaction identifier). |
| * |
| * @return a string that can be used as a transaction identifier for this message. This can be |
| * used for matching responses and requests (i.e. an outgoing request and its matching |
| * response have the same computed transaction identifier). |
| */ |
| public String getTransactionId() { |
| Via topVia = null; |
| if (!this.getViaHeaders().isEmpty()) { |
| topVia = (Via) this.getViaHeaders().getFirst(); |
| } |
| // Have specified a branch Identifier so we can use it to identify |
| // the transaction. BranchId is not case sensitive. |
| // Branch Id prefix is not case sensitive. |
| if (topVia != null |
| && topVia.getBranch() != null |
| && topVia.getBranch().toUpperCase().startsWith( |
| SIPConstants.BRANCH_MAGIC_COOKIE_UPPER_CASE)) { |
| // Bis 09 compatible branch assignment algorithm. |
| // implies that the branch id can be used as a transaction |
| // identifier. |
| if (this.getCSeq().getMethod().equals(Request.CANCEL)) |
| return (topVia.getBranch() + ":" + this.getCSeq().getMethod()).toLowerCase(); |
| else |
| return topVia.getBranch().toLowerCase(); |
| } else { |
| // Old style client so construct the transaction identifier |
| // from various fields of the request. |
| StringBuffer retval = new StringBuffer(); |
| From from = (From) this.getFrom(); |
| To to = (To) this.getTo(); |
| // String hpFrom = from.getUserAtHostPort(); |
| // retval.append(hpFrom).append(":"); |
| if (from.hasTag()) |
| retval.append(from.getTag()).append("-"); |
| // String hpTo = to.getUserAtHostPort(); |
| // retval.append(hpTo).append(":"); |
| String cid = this.callIdHeader.getCallId(); |
| retval.append(cid).append("-"); |
| retval.append(this.cSeqHeader.getSequenceNumber()).append("-").append( |
| this.cSeqHeader.getMethod()); |
| if (topVia != null) { |
| retval.append("-").append(topVia.getSentBy().encode()); |
| if (!topVia.getSentBy().hasPort()) { |
| retval.append("-").append(5060); |
| } |
| } |
| if (this.getCSeq().getMethod().equals(Request.CANCEL)) { |
| retval.append(Request.CANCEL); |
| } |
| return retval.toString().toLowerCase().replace(":", "-").replace("@", "-") |
| + Utils.getSignature(); |
| } |
| } |
| |
| /** |
| * Override the hashcode method ( see issue # 55 ) Note that if you try to use this method |
| * before you assemble a valid request, you will get a constant ( -1 ). Beware of placing any |
| * half formed requests in a table. |
| */ |
| public int hashCode() { |
| if (this.callIdHeader == null) |
| throw new RuntimeException( |
| "Invalid message! Cannot compute hashcode! call-id header is missing !"); |
| else |
| return this.callIdHeader.getCallId().hashCode(); |
| } |
| |
| /** |
| * Return true if this message has a body. |
| */ |
| public boolean hasContent() { |
| return messageContent != null || messageContentBytes != null; |
| } |
| |
| /** |
| * Return an iterator for the list of headers in this message. |
| * |
| * @return an Iterator for the headers of this message. |
| */ |
| public Iterator<SIPHeader> getHeaders() { |
| return headers.iterator(); |
| } |
| |
| /** |
| * Get the first header of the given name. |
| * |
| * @return header -- the first header of the given name. |
| */ |
| public Header getHeader(String headerName) { |
| return getHeaderLowerCase(SIPHeaderNamesCache.toLowerCase(headerName)); |
| } |
| |
| private Header getHeaderLowerCase(String lowerCaseHeaderName) { |
| if (lowerCaseHeaderName == null) |
| throw new NullPointerException("bad name"); |
| SIPHeader sipHeader = (SIPHeader) nameTable.get(lowerCaseHeaderName); |
| if (sipHeader instanceof SIPHeaderList) |
| return (Header) ((SIPHeaderList) sipHeader).getFirst(); |
| else |
| return (Header) sipHeader; |
| } |
| |
| /** |
| * Get the contentType header (null if one does not exist). |
| * |
| * @return contentType header |
| */ |
| |
| public ContentType getContentTypeHeader() { |
| return (ContentType) getHeaderLowerCase(CONTENT_TYPE_LOWERCASE); |
| } |
| |
| private static final String CONTENT_TYPE_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ContentTypeHeader.NAME); |
| |
| |
| /** |
| * Get the contentLength header. |
| */ |
| public ContentLengthHeader getContentLengthHeader() { |
| return this.getContentLength(); |
| } |
| |
| |
| /** |
| * Get the from header. |
| * |
| * @return -- the from header. |
| */ |
| public FromHeader getFrom() { |
| return (FromHeader) fromHeader; |
| } |
| |
| /** |
| * Get the ErrorInfo list of headers (null if one does not exist). |
| * |
| * @return List containing ErrorInfo headers. |
| */ |
| public ErrorInfoList getErrorInfoHeaders() { |
| return (ErrorInfoList) getSIPHeaderListLowerCase(ERROR_LOWERCASE); |
| } |
| |
| private static final String ERROR_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ErrorInfo.NAME); |
| |
| /** |
| * Get the Contact list of headers (null if one does not exist). |
| * |
| * @return List containing Contact headers. |
| */ |
| public ContactList getContactHeaders() { |
| return (ContactList) this.getSIPHeaderListLowerCase(CONTACT_LOWERCASE); |
| } |
| |
| private static final String CONTACT_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ContactHeader.NAME); |
| |
| /** |
| * Get the contact header ( the first contact header) which is all we need for the most part. |
| * |
| */ |
| public Contact getContactHeader() { |
| ContactList clist = this.getContactHeaders(); |
| if (clist != null) { |
| return (Contact) clist.getFirst(); |
| |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Get the Via list of headers (null if one does not exist). |
| * |
| * @return List containing Via headers. |
| */ |
| public ViaList getViaHeaders() { |
| return (ViaList) getSIPHeaderListLowerCase(VIA_LOWERCASE); |
| } |
| |
| private static final String VIA_LOWERCASE = SIPHeaderNamesCache.toLowerCase(ViaHeader.NAME); |
| |
| /** |
| * Set A list of via headers. |
| * |
| * @param viaList a list of via headers to add. |
| */ |
| public void setVia(java.util.List viaList) { |
| ViaList vList = new ViaList(); |
| ListIterator it = viaList.listIterator(); |
| while (it.hasNext()) { |
| Via via = (Via) it.next(); |
| vList.add(via); |
| } |
| this.setHeader(vList); |
| } |
| |
| /** |
| * Set the header given a list of headers. |
| * |
| * @param sipHeaderList a headerList to set |
| */ |
| |
| public void setHeader(SIPHeaderList<Via> sipHeaderList) { |
| this.setHeader((Header) sipHeaderList); |
| } |
| |
| /** |
| * Get the topmost via header. |
| * |
| * @return the top most via header if one exists or null if none exists. |
| */ |
| public Via getTopmostVia() { |
| if (this.getViaHeaders() == null) |
| return null; |
| else |
| return (Via) (getViaHeaders().getFirst()); |
| } |
| |
| /** |
| * Get the CSeq list of header (null if one does not exist). |
| * |
| * @return CSeq header |
| */ |
| public CSeqHeader getCSeq() { |
| return (CSeqHeader) cSeqHeader; |
| } |
| |
| /** |
| * Get the Authorization header (null if one does not exist). |
| * |
| * @return Authorization header. |
| */ |
| public Authorization getAuthorization() { |
| return (Authorization) getHeaderLowerCase(AUTHORIZATION_LOWERCASE); |
| } |
| |
| private static final String AUTHORIZATION_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(AuthorizationHeader.NAME); |
| |
| /** |
| * Get the MaxForwards header (null if one does not exist). |
| * |
| * @return Max-Forwards header |
| */ |
| |
| public MaxForwardsHeader getMaxForwards() { |
| return maxForwardsHeader; |
| } |
| |
| /** |
| * Set the max forwards header. |
| * |
| * @param maxForwards is the MaxForwardsHeader to set. |
| */ |
| public void setMaxForwards(MaxForwardsHeader maxForwards) { |
| this.setHeader(maxForwards); |
| } |
| |
| /** |
| * Get the Route List of headers (null if one does not exist). |
| * |
| * @return List containing Route headers |
| */ |
| public RouteList getRouteHeaders() { |
| return (RouteList) getSIPHeaderListLowerCase(ROUTE_LOWERCASE); |
| } |
| |
| private static final String ROUTE_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(RouteHeader.NAME); |
| |
| /** |
| * Get the CallID header (null if one does not exist) |
| * |
| * @return Call-ID header . |
| */ |
| public CallIdHeader getCallId() { |
| return callIdHeader; |
| } |
| |
| /** |
| * Set the call id header. |
| * |
| * @param callId call idHeader (what else could it be?) |
| */ |
| public void setCallId(CallIdHeader callId) { |
| this.setHeader(callId); |
| } |
| |
| /** |
| * Get the CallID header (null if one does not exist) |
| * |
| * @param callId -- the call identifier to be assigned to the call id header |
| */ |
| public void setCallId(String callId) throws java.text.ParseException { |
| if (callIdHeader == null) { |
| this.setHeader(new CallID()); |
| } |
| callIdHeader.setCallId(callId); |
| } |
| |
| /** |
| * Get the RecordRoute header list (null if one does not exist). |
| * |
| * @return Record-Route header |
| */ |
| public RecordRouteList getRecordRouteHeaders() { |
| return (RecordRouteList) this.getSIPHeaderListLowerCase(RECORDROUTE_LOWERCASE); |
| } |
| |
| private static final String RECORDROUTE_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(RecordRouteHeader.NAME); |
| |
| /** |
| * Get the To header (null if one does not exist). |
| * |
| * @return To header |
| */ |
| public ToHeader getTo() { |
| return (ToHeader) toHeader; |
| } |
| |
| public void setTo(ToHeader to) { |
| this.setHeader(to); |
| } |
| |
| public void setFrom(FromHeader from) { |
| this.setHeader(from); |
| |
| } |
| |
| /** |
| * Get the ContentLength header (null if one does not exist). |
| * |
| * @return content-length header. |
| */ |
| public ContentLengthHeader getContentLength() { |
| return this.contentLengthHeader; |
| } |
| |
| /** |
| * Get the message body as a string. If the message contains a content type header with a |
| * specified charset, and if the payload has been read as a byte array, then it is returned |
| * encoded into this charset. |
| * |
| * @return Message body (as a string) |
| * @throws UnsupportedEncodingException if the platform does not support the charset specified |
| * in the content type header. |
| * |
| */ |
| public String getMessageContent() throws UnsupportedEncodingException { |
| if (this.messageContent == null && this.messageContentBytes == null) |
| return null; |
| else if (this.messageContent == null) { |
| this.messageContent = new String(messageContentBytes, getCharset() ); |
| } |
| return this.messageContent; |
| } |
| |
| /** |
| * Get the message content as an array of bytes. If the payload has been read as a String then |
| * it is decoded using the charset specified in the content type header if it exists. |
| * Otherwise, it is encoded using the default encoding which is UTF-8. |
| * |
| * @return an array of bytes that is the message payload. |
| */ |
| public byte[] getRawContent() { |
| try { |
| if ( this.messageContentBytes != null ) { |
| // return messageContentBytes; |
| } else if (this.messageContentObject != null) { |
| String messageContent = this.messageContentObject.toString(); |
| this.messageContentBytes = messageContent.getBytes( getCharset() ); |
| } else if (this.messageContent != null) { |
| this.messageContentBytes = messageContent.getBytes( getCharset() ); |
| } |
| return this.messageContentBytes; |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| return null; |
| } |
| } |
| |
| /** |
| * Set the message content given type and subtype. |
| * |
| * @param type is the message type (eg. application) |
| * @param subType is the message sybtype (eg. sdp) |
| * @param messageContent is the messge content as a string. |
| */ |
| public void setMessageContent(String type, String subType, String messageContent) { |
| if (messageContent == null) |
| throw new IllegalArgumentException("messgeContent is null"); |
| ContentType ct = new ContentType(type, subType); |
| this.setHeader(ct); |
| this.messageContent = messageContent; |
| this.messageContentBytes = null; |
| this.messageContentObject = null; |
| // Could be double byte so we need to compute length |
| // after converting to byte[] |
| computeContentLength(messageContent); |
| } |
| |
| /** |
| * Set the message content after converting the given object to a String. |
| * |
| * @param content -- content to set. |
| * @param contentTypeHeader -- content type header corresponding to content. |
| */ |
| public void setContent(Object content, ContentTypeHeader contentTypeHeader) |
| throws ParseException { |
| if (content == null) |
| throw new NullPointerException("null content"); |
| this.setHeader(contentTypeHeader); |
| |
| this.messageContent = null; |
| this.messageContentBytes = null; |
| this.messageContentObject = null; |
| |
| if (content instanceof String) { |
| this.messageContent = (String) content; |
| } else if (content instanceof byte[]) { |
| this.messageContentBytes = (byte[]) content; |
| } else |
| this.messageContentObject = content; |
| |
| computeContentLength(content); |
| } |
| |
| /** |
| * Get the content (body) of the message. |
| * |
| * @return the content of the sip message. |
| */ |
| public Object getContent() { |
| if (this.messageContentObject != null) |
| return messageContentObject; |
| else if (this.messageContent != null) |
| return this.messageContent; |
| else if (this.messageContentBytes != null) |
| return this.messageContentBytes; |
| else |
| return null; |
| } |
| |
| /** |
| * Set the message content for a given type and subtype. |
| * |
| * @param type is the messge type. |
| * @param subType is the message subType. |
| * @param messageContent is the message content as a byte array. |
| */ |
| public void setMessageContent(String type, String subType, byte[] messageContent) { |
| ContentType ct = new ContentType(type, subType); |
| this.setHeader(ct); |
| this.setMessageContent(messageContent); |
| |
| computeContentLength(messageContent); |
| } |
| |
| /** |
| * Set the message content for this message. |
| * |
| * @param content Message body as a string. |
| */ |
| public void setMessageContent(String content, boolean strict, boolean computeContentLength, int givenLength) |
| throws ParseException { |
| // Note that that this could be a double byte character |
| // set - bug report by Masafumi Watanabe |
| computeContentLength(content); |
| if ((!computeContentLength)) { |
| if ( (!strict && this.contentLengthHeader.getContentLength() != givenLength) |
| || this.contentLengthHeader.getContentLength() < givenLength) { |
| throw new ParseException("Invalid content length " |
| + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0); |
| } |
| } |
| |
| messageContent = content; |
| messageContentBytes = null; |
| messageContentObject = null; |
| } |
| |
| /** |
| * Set the message content as an array of bytes. |
| * |
| * @param content is the content of the message as an array of bytes. |
| */ |
| public void setMessageContent(byte[] content) { |
| computeContentLength(content); |
| |
| messageContentBytes = content; |
| messageContent = null; |
| messageContentObject = null; |
| } |
| |
| /** |
| * Method to set the content - called by the parser |
| * |
| * @param content |
| * @throws ParseException |
| */ |
| public void setMessageContent(byte[] content, boolean computeContentLength, int givenLength) |
| throws ParseException { |
| computeContentLength(content); |
| if ((!computeContentLength) && this.contentLengthHeader.getContentLength() < givenLength) { |
| // System.out.println("!!!!!!!!!!! MISMATCH !!!!!!!!!!!"); |
| throw new ParseException("Invalid content length " |
| + this.contentLengthHeader.getContentLength() + " / " + givenLength, 0); |
| } |
| messageContentBytes = content; |
| messageContent = null; |
| messageContentObject = null; |
| } |
| |
| /** |
| * Compute and set the Content-length header based on the given content object. |
| * |
| * @param content is the content, as String, array of bytes, or other object. |
| */ |
| private void computeContentLength(Object content) { |
| int length = 0; |
| if (content != null) { |
| if (content instanceof String) { |
| try { |
| length = ((String) content).getBytes( getCharset() ).length; |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| } else if (content instanceof byte[]) { |
| length = ((byte[]) content).length; |
| } else { |
| length = content.toString().length(); |
| } |
| } |
| |
| try { |
| contentLengthHeader.setContentLength(length); |
| } catch (InvalidArgumentException e) { |
| // Cannot happen. |
| } |
| } |
| |
| /** |
| * Remove the message content if it exists. |
| */ |
| public void removeContent() { |
| messageContent = null; |
| messageContentBytes = null; |
| messageContentObject = null; |
| try { |
| this.contentLengthHeader.setContentLength(0); |
| } catch (InvalidArgumentException ex) { |
| } |
| } |
| |
| /** |
| * Get a SIP header or Header list given its name. |
| * |
| * @param headerName is the name of the header to get. |
| * @return a header or header list that contians the retrieved header. |
| */ |
| @SuppressWarnings("unchecked") |
| public ListIterator<SIPHeader> getHeaders(String headerName) { |
| if (headerName == null) |
| throw new NullPointerException("null headerName"); |
| SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache |
| .toLowerCase(headerName)); |
| // empty iterator |
| if (sipHeader == null) |
| return new LinkedList<SIPHeader>().listIterator(); |
| if (sipHeader instanceof SIPHeaderList) { |
| return ((SIPHeaderList<SIPHeader>) sipHeader).listIterator(); |
| } else { |
| return new HeaderIterator(this, sipHeader); |
| } |
| } |
| |
| /** |
| * Get a header of the given name as a string. This concatenates the headers of a given type |
| * as a comma separted list. This is useful for formatting and printing headers. |
| * |
| * @param name |
| * @return the header as a formatted string |
| */ |
| public String getHeaderAsFormattedString(String name) { |
| String lowerCaseName = name.toLowerCase(); |
| if (this.nameTable.containsKey(lowerCaseName)) { |
| return this.nameTable.get(lowerCaseName).toString(); |
| } else { |
| return this.getHeader(name).toString(); |
| } |
| } |
| |
| private SIPHeader getSIPHeaderListLowerCase(String lowerCaseHeaderName) { |
| return nameTable.get(lowerCaseHeaderName); |
| } |
| |
| /** |
| * Get a list of headers of the given name ( or null if no such header exists ). |
| * |
| * @param headerName -- a header name from which to retrieve the list. |
| * @return -- a list of headers with that name. |
| */ |
| @SuppressWarnings("unchecked") |
| private List<SIPHeader> getHeaderList(String headerName) { |
| SIPHeader sipHeader = (SIPHeader) nameTable.get(SIPHeaderNamesCache |
| .toLowerCase(headerName)); |
| if (sipHeader == null) |
| return null; |
| else if (sipHeader instanceof SIPHeaderList) |
| return (List<SIPHeader>) (((SIPHeaderList< ? >) sipHeader).getHeaderList()); |
| else { |
| LinkedList<SIPHeader> ll = new LinkedList<SIPHeader>(); |
| ll.add(sipHeader); |
| return ll; |
| } |
| } |
| |
| /** |
| * Return true if the SIPMessage has a header of the given name. |
| * |
| * @param headerName is the header name for which we are testing. |
| * @return true if the header is present in the message |
| */ |
| public boolean hasHeader(String headerName) { |
| return nameTable.containsKey(SIPHeaderNamesCache.toLowerCase(headerName)); |
| } |
| |
| /** |
| * Return true if the message has a From header tag. |
| * |
| * @return true if the message has a from header and that header has a tag. |
| */ |
| public boolean hasFromTag() { |
| return fromHeader != null && fromHeader.getTag() != null; |
| } |
| |
| /** |
| * Return true if the message has a To header tag. |
| * |
| * @return true if the message has a to header and that header has a tag. |
| */ |
| public boolean hasToTag() { |
| return toHeader != null && toHeader.getTag() != null; |
| } |
| |
| /** |
| * Return the from tag. |
| * |
| * @return the tag from the from header. |
| * |
| */ |
| public String getFromTag() { |
| return fromHeader == null ? null : fromHeader.getTag(); |
| } |
| |
| /** |
| * Set the From Tag. |
| * |
| * @param tag -- tag to set in the from header. |
| */ |
| public void setFromTag(String tag) { |
| try { |
| fromHeader.setTag(tag); |
| } catch (ParseException e) { |
| } |
| } |
| |
| /** |
| * Set the to tag. |
| * |
| * @param tag -- tag to set. |
| */ |
| public void setToTag(String tag) { |
| try { |
| toHeader.setTag(tag); |
| } catch (ParseException e) { |
| } |
| } |
| |
| /** |
| * Return the to tag. |
| */ |
| public String getToTag() { |
| return toHeader == null ? null : toHeader.getTag(); |
| } |
| |
| /** |
| * Return the encoded first line. |
| */ |
| public abstract String getFirstLine(); |
| |
| /** |
| * Add a SIP header. |
| * |
| * @param sipHeader -- sip header to add. |
| */ |
| public void addHeader(Header sipHeader) { |
| // Content length is never stored. Just computed. |
| SIPHeader sh = (SIPHeader) sipHeader; |
| try { |
| if ((sipHeader instanceof ViaHeader) || (sipHeader instanceof RecordRouteHeader)) { |
| attachHeader(sh, false, true); |
| } else { |
| attachHeader(sh, false, false); |
| } |
| } catch (SIPDuplicateHeaderException ex) { |
| try { |
| if (sipHeader instanceof ContentLength) { |
| ContentLength cl = (ContentLength) sipHeader; |
| contentLengthHeader.setContentLength(cl.getContentLength()); |
| } |
| } catch (InvalidArgumentException e) { |
| } |
| } |
| } |
| |
| /** |
| * Add a header to the unparsed list of headers. |
| * |
| * @param unparsed -- unparsed header to add to the list. |
| */ |
| public void addUnparsed(String unparsed) { |
| this.unrecognizedHeaders.add(unparsed); |
| } |
| |
| /** |
| * Add a SIP header. |
| * |
| * @param sipHeader -- string version of SIP header to add. |
| */ |
| |
| public void addHeader(String sipHeader) { |
| String hdrString = sipHeader.trim() + "\n"; |
| try { |
| HeaderParser parser = ParserFactory.createParser(sipHeader); |
| SIPHeader sh = parser.parse(); |
| this.attachHeader(sh, false); |
| } catch (ParseException ex) { |
| this.unrecognizedHeaders.add(hdrString); |
| } |
| } |
| |
| /** |
| * Get a list containing the unrecognized headers. |
| * |
| * @return a linked list containing unrecongnized headers. |
| */ |
| public ListIterator<String> getUnrecognizedHeaders() { |
| return this.unrecognizedHeaders.listIterator(); |
| } |
| |
| /** |
| * Get the header names. |
| * |
| * @return a list iterator to a list of header names. These are ordered in the same order as |
| * are present in the message. |
| */ |
| public ListIterator<String> getHeaderNames() { |
| Iterator<SIPHeader> li = this.headers.iterator(); |
| LinkedList<String> retval = new LinkedList<String>(); |
| while (li.hasNext()) { |
| SIPHeader sipHeader = (SIPHeader) li.next(); |
| String name = sipHeader.getName(); |
| retval.add(name); |
| } |
| return retval.listIterator(); |
| } |
| |
| /** |
| * Compare for equality. |
| * |
| * @param other -- the other object to compare with. |
| */ |
| public boolean equals(Object other) { |
| if (!other.getClass().equals(this.getClass())) { |
| return false; |
| } |
| SIPMessage otherMessage = (SIPMessage) other; |
| Collection<SIPHeader> values = this.nameTable.values(); |
| Iterator<SIPHeader> it = values.iterator(); |
| if (nameTable.size() != otherMessage.nameTable.size()) { |
| return false; |
| } |
| |
| while (it.hasNext()) { |
| SIPHeader mine = (SIPHeader) it.next(); |
| SIPHeader his = (SIPHeader) (otherMessage.nameTable.get(SIPHeaderNamesCache |
| .toLowerCase(mine.getName()))); |
| if (his == null) { |
| return false; |
| } else if (!his.equals(mine)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * get content disposition header or null if no such header exists. |
| * |
| * @return the contentDisposition header |
| */ |
| public javax.sip.header.ContentDispositionHeader getContentDisposition() { |
| return (ContentDispositionHeader) getHeaderLowerCase(CONTENT_DISPOSITION_LOWERCASE); |
| } |
| |
| private static final String CONTENT_DISPOSITION_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ContentDispositionHeader.NAME); |
| |
| /** |
| * get the content encoding header. |
| * |
| * @return the contentEncoding header. |
| */ |
| public javax.sip.header.ContentEncodingHeader getContentEncoding() { |
| return (ContentEncodingHeader) getHeaderLowerCase(CONTENT_ENCODING_LOWERCASE); |
| } |
| |
| private static final String CONTENT_ENCODING_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ContentEncodingHeader.NAME); |
| |
| /** |
| * Get the contentLanguage header. |
| * |
| * @return the content language header. |
| */ |
| public javax.sip.header.ContentLanguageHeader getContentLanguage() { |
| return (ContentLanguageHeader) getHeaderLowerCase(CONTENT_LANGUAGE_LOWERCASE); |
| } |
| |
| private static final String CONTENT_LANGUAGE_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ContentLanguageHeader.NAME); |
| |
| /** |
| * Get the exipres header. |
| * |
| * @return the expires header or null if one does not exist. |
| */ |
| public javax.sip.header.ExpiresHeader getExpires() { |
| return (ExpiresHeader) getHeaderLowerCase(EXPIRES_LOWERCASE); |
| } |
| |
| private static final String EXPIRES_LOWERCASE = SIPHeaderNamesCache |
| .toLowerCase(ExpiresHeader.NAME); |
| |
| /** |
| * Set the expiresHeader |
| * |
| * @param expiresHeader -- the expires header to set. |
| */ |
| |
| public void setExpires(ExpiresHeader expiresHeader) { |
| this.setHeader(expiresHeader); |
| } |
| |
| /** |
| * Set the content disposition header. |
| * |
| * @param contentDispositionHeader -- content disposition header. |
| */ |
| |
| public void setContentDisposition(ContentDispositionHeader contentDispositionHeader) { |
| this.setHeader(contentDispositionHeader); |
| |
| } |
| |
| public void setContentEncoding(ContentEncodingHeader contentEncodingHeader) { |
| this.setHeader(contentEncodingHeader); |
| |
| } |
| |
| public void setContentLanguage(ContentLanguageHeader contentLanguageHeader) { |
| this.setHeader(contentLanguageHeader); |
| } |
| |
| /** |
| * Set the content length header. |
| * |
| * @param contentLength -- content length header. |
| */ |
| public void setContentLength(ContentLengthHeader contentLength) { |
| try { |
| this.contentLengthHeader.setContentLength(contentLength.getContentLength()); |
| } catch (InvalidArgumentException ex) { |
| } |
| |
| } |
| |
| /** |
| * Set the size of all the headers. This is for book keeping. Called by the parser. |
| * |
| * @param size -- size of the headers. |
| */ |
| public void setSize(int size) { |
| this.size = size; |
| } |
| |
| public int getSize() { |
| return this.size; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see javax.sip.message.Message#addLast(javax.sip.header.Header) |
| */ |
| public void addLast(Header header) throws SipException, NullPointerException { |
| if (header == null) |
| throw new NullPointerException("null arg!"); |
| |
| try { |
| this.attachHeader((SIPHeader) header, false, false); |
| } catch (SIPDuplicateHeaderException ex) { |
| throw new SipException("Cannot add header - header already exists"); |
| } |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see javax.sip.message.Message#addFirst(javax.sip.header.Header) |
| */ |
| public void addFirst(Header header) throws SipException, NullPointerException { |
| |
| if (header == null) |
| throw new NullPointerException("null arg!"); |
| |
| try { |
| this.attachHeader((SIPHeader) header, false, true); |
| } catch (SIPDuplicateHeaderException ex) { |
| throw new SipException("Cannot add header - header already exists"); |
| } |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see javax.sip.message.Message#removeFirst(java.lang.String) |
| */ |
| public void removeFirst(String headerName) throws NullPointerException { |
| if (headerName == null) |
| throw new NullPointerException("Null argument Provided!"); |
| this.removeHeader(headerName, true); |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see javax.sip.message.Message#removeLast(java.lang.String) |
| */ |
| public void removeLast(String headerName) { |
| if (headerName == null) |
| throw new NullPointerException("Null argument Provided!"); |
| this.removeHeader(headerName, false); |
| |
| } |
| |
| /** |
| * Set the CSeq header. |
| * |
| * @param cseqHeader -- CSeq Header. |
| */ |
| |
| public void setCSeq(CSeqHeader cseqHeader) { |
| this.setHeader(cseqHeader); |
| } |
| |
| /** |
| * Set the application data pointer. This method is not used the stack. It is provided as a |
| * convenient way of storing book-keeping data for applications. Note that null clears the |
| * application data pointer (releases it). |
| * |
| * @param applicationData -- application data pointer to set. null clears the application data |
| * pointer. |
| */ |
| public void setApplicationData(Object applicationData) { |
| this.applicationData = applicationData; |
| } |
| |
| /** |
| * Get the application data associated with this message. |
| * |
| * @return stored application data. |
| */ |
| public Object getApplicationData() { |
| return this.applicationData; |
| } |
| |
| /** |
| * Get the multipart MIME content |
| * |
| */ |
| public MultipartMimeContent getMultipartMimeContent() throws ParseException { |
| if (this.contentLengthHeader.getContentLength() == 0) { |
| return null; |
| } |
| MultipartMimeContentImpl retval = new MultipartMimeContentImpl(this |
| .getContentTypeHeader()); |
| byte[] rawContent = getRawContent(); |
| try { |
| String body = new String( rawContent, getCharset() ); |
| retval.createContentList(body); |
| return retval; |
| } catch (UnsupportedEncodingException e) { |
| InternalErrorHandler.handleException(e); |
| return null; |
| } |
| } |
| |
| public CallIdHeader getCallIdHeader() { |
| return this.callIdHeader; |
| } |
| |
| |
| public FromHeader getFromHeader() { |
| return this.fromHeader; |
| } |
| |
| |
| public ToHeader getToHeader() { |
| return this.toHeader; |
| } |
| |
| |
| public ViaHeader getTopmostViaHeader() { |
| return this.getTopmostVia(); |
| } |
| |
| public CSeqHeader getCSeqHeader() { |
| return this.cSeqHeader; |
| } |
| |
| /** |
| * Returns the charset to use for encoding/decoding the body of this message |
| */ |
| protected final String getCharset() { |
| ContentType ct = getContentTypeHeader(); |
| if (ct!=null) { |
| String c = ct.getCharset(); |
| return c!=null ? c : contentEncodingCharset; |
| } else return contentEncodingCharset; |
| } |
| |
| /** |
| * Return true if this is a null request (i.e. does not have a request line ). |
| * |
| * @return true if null request. |
| */ |
| public boolean isNullRequest() { |
| return this.nullRequest; |
| } |
| |
| /** |
| * Set a flag to indiate this is a special message ( encoded with CRLFCRLF ). |
| * |
| */ |
| public void setNullRequest() { |
| this.nullRequest = true; |
| } |
| |
| |
| public abstract void setSIPVersion(String sipVersion) throws ParseException; |
| |
| public abstract String getSIPVersion(); |
| |
| public abstract String toString(); |
| |
| } |