| /* |
| * 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.javax.sip.address.*; |
| import gov.nist.core.*; |
| |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.LinkedList; |
| import java.util.Set; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Iterator; |
| import javax.sip.address.URI; |
| import javax.sip.message.*; |
| |
| import java.text.ParseException; |
| import javax.sip.*; |
| import javax.sip.header.*; |
| |
| import gov.nist.javax.sip.header.*; |
| import gov.nist.javax.sip.stack.SIPTransactionStack; |
| |
| /* |
| * Acknowledgements: Mark Bednarek made a few fixes to this code. Jeff Keyser added two methods |
| * that create responses and generate cancel requests from incoming orignial requests without the |
| * additional overhead of encoding and decoding messages. Bruno Konik noticed an extraneous |
| * newline added to the end of the buffer when encoding it. Incorporates a bug report from Andreas |
| * Bystrom. Szabo Barna noticed a contact in a cancel request - this is a pointless header for |
| * cancel. Antonis Kyardis contributed bug fixes. Jeroen van Bemmel noted that method names are |
| * case sensitive, should use equals() in getting CannonicalName |
| * |
| */ |
| |
| /** |
| * The SIP Request structure. |
| * |
| * @version 1.2 $Revision: 1.52 $ $Date: 2009/12/16 14:58:40 $ |
| * @since 1.1 |
| * |
| * @author M. Ranganathan <br/> |
| * |
| * |
| * |
| */ |
| |
| public final class SIPRequest extends SIPMessage implements javax.sip.message.Request, RequestExt { |
| |
| private static final long serialVersionUID = 3360720013577322927L; |
| |
| private static final String DEFAULT_USER = "ip"; |
| |
| private static final String DEFAULT_TRANSPORT = "udp"; |
| |
| private transient Object transactionPointer; |
| |
| private RequestLine requestLine; |
| |
| private transient Object messageChannel; |
| |
| |
| |
| private transient Object inviteTransaction; // The original invite request for a |
| // given cancel request |
| |
| /** |
| * Set of target refresh methods, currently: INVITE, UPDATE, SUBSCRIBE, NOTIFY, REFER |
| * |
| * A target refresh request and its response MUST have a Contact |
| */ |
| private static final Set<String> targetRefreshMethods = new HashSet<String>(); |
| |
| /* |
| * A table that maps a name string to its cannonical constant. This is used to speed up |
| * parsing of messages .equals reduces to == if we use the constant value. |
| */ |
| private static final Hashtable<String, String> nameTable = new Hashtable<String, String>(); |
| |
| private static void putName(String name) { |
| nameTable.put(name, name); |
| } |
| |
| static { |
| targetRefreshMethods.add(Request.INVITE); |
| targetRefreshMethods.add(Request.UPDATE); |
| targetRefreshMethods.add(Request.SUBSCRIBE); |
| targetRefreshMethods.add(Request.NOTIFY); |
| targetRefreshMethods.add(Request.REFER); |
| |
| putName(Request.INVITE); |
| putName(Request.BYE); |
| putName(Request.CANCEL); |
| putName(Request.ACK); |
| putName(Request.PRACK); |
| putName(Request.INFO); |
| putName(Request.MESSAGE); |
| putName(Request.NOTIFY); |
| putName(Request.OPTIONS); |
| putName(Request.PRACK); |
| putName(Request.PUBLISH); |
| putName(Request.REFER); |
| putName(Request.REGISTER); |
| putName(Request.SUBSCRIBE); |
| putName(Request.UPDATE); |
| |
| } |
| |
| /** |
| * @return true iff the method is a target refresh |
| */ |
| public static boolean isTargetRefresh(String ucaseMethod) { |
| return targetRefreshMethods.contains(ucaseMethod); |
| } |
| |
| /** |
| * @return true iff the method is a dialog creating method |
| */ |
| public static boolean isDialogCreating(String ucaseMethod) { |
| return SIPTransactionStack.isDialogCreated(ucaseMethod); |
| } |
| |
| /** |
| * Set to standard constants to speed up processing. this makes equals comparisons run much |
| * faster in the stack because then it is just identity comparision. Character by char |
| * comparison is not required. The method returns the String CONSTANT corresponding to the |
| * String name. |
| * |
| */ |
| public static String getCannonicalName(String method) { |
| |
| if (nameTable.containsKey(method)) |
| return (String) nameTable.get(method); |
| else |
| return method; |
| } |
| |
| /** |
| * Get the Request Line of the SIPRequest. |
| * |
| * @return the request line of the SIP Request. |
| */ |
| |
| public RequestLine getRequestLine() { |
| return requestLine; |
| } |
| |
| /** |
| * Set the request line of the SIP Request. |
| * |
| * @param requestLine is the request line to set in the SIP Request. |
| */ |
| |
| public void setRequestLine(RequestLine requestLine) { |
| this.requestLine = requestLine; |
| } |
| |
| /** |
| * Constructor. |
| */ |
| public SIPRequest() { |
| super(); |
| } |
| |
| /** |
| * Convert to a formatted string for pretty printing. Note that the encode method converts |
| * this into a sip message that is suitable for transmission. Note hack here if you want to |
| * convert the nice curly brackets into some grotesque XML tag. |
| * |
| * @return a string which can be used to examine the message contents. |
| * |
| */ |
| public String debugDump() { |
| String superstring = super.debugDump(); |
| stringRepresentation = ""; |
| sprint(SIPRequest.class.getName()); |
| sprint("{"); |
| if (requestLine != null) |
| sprint(requestLine.debugDump()); |
| sprint(superstring); |
| sprint("}"); |
| return stringRepresentation; |
| } |
| |
| /** |
| * Check header for constraints. (1) Invite options and bye requests can only have SIP URIs in |
| * the contact headers. (2) Request must have cseq, to and from and via headers. (3) Method in |
| * request URI must match that in CSEQ. |
| */ |
| public void checkHeaders() throws ParseException { |
| String prefix = "Missing a required header : "; |
| |
| /* Check for required headers */ |
| |
| if (getCSeq() == null) { |
| throw new ParseException(prefix + CSeqHeader.NAME, 0); |
| } |
| if (getTo() == null) { |
| throw new ParseException(prefix + ToHeader.NAME, 0); |
| } |
| |
| if (this.callIdHeader == null || this.callIdHeader.getCallId() == null |
| || callIdHeader.getCallId().equals("")) { |
| throw new ParseException(prefix + CallIdHeader.NAME, 0); |
| } |
| if (getFrom() == null) { |
| throw new ParseException(prefix + FromHeader.NAME, 0); |
| } |
| if (getViaHeaders() == null) { |
| throw new ParseException(prefix + ViaHeader.NAME, 0); |
| } |
| // BEGIN android-deleted |
| /* |
| if (getMaxForwards() == null) { |
| throw new ParseException(prefix + MaxForwardsHeader.NAME, 0); |
| } |
| */ |
| // END android-deleted |
| |
| if (getTopmostVia() == null) |
| throw new ParseException("No via header in request! ", 0); |
| |
| if (getMethod().equals(Request.NOTIFY)) { |
| if (getHeader(SubscriptionStateHeader.NAME) == null) |
| throw new ParseException(prefix + SubscriptionStateHeader.NAME, 0); |
| |
| if (getHeader(EventHeader.NAME) == null) |
| throw new ParseException(prefix + EventHeader.NAME, 0); |
| |
| } else if (getMethod().equals(Request.PUBLISH)) { |
| /* |
| * For determining the type of the published event state, the EPA MUST include a |
| * single Event header field in PUBLISH requests. The value of this header field |
| * indicates the event package for which this request is publishing event state. |
| */ |
| if (getHeader(EventHeader.NAME) == null) |
| throw new ParseException(prefix + EventHeader.NAME, 0); |
| } |
| |
| /* |
| * RFC 3261 8.1.1.8 The Contact header field MUST be present and contain exactly one SIP |
| * or SIPS URI in any request that can result in the establishment of a dialog. For the |
| * methods defined in this specification, that includes only the INVITE request. For these |
| * requests, the scope of the Contact is global. That is, the Contact header field value |
| * contains the URI at which the UA would like to receive requests, and this URI MUST be |
| * valid even if used in subsequent requests outside of any dialogs. |
| * |
| * If the Request-URI or top Route header field value contains a SIPS URI, the Contact |
| * header field MUST contain a SIPS URI as well. |
| */ |
| if (requestLine.getMethod().equals(Request.INVITE) |
| || requestLine.getMethod().equals(Request.SUBSCRIBE) |
| || requestLine.getMethod().equals(Request.REFER)) { |
| if (this.getContactHeader() == null) { |
| // Make sure this is not a target refresh. If this is a target |
| // refresh its ok not to have a contact header. Otherwise |
| // contact header is mandatory. |
| if (this.getToTag() == null) |
| throw new ParseException(prefix + ContactHeader.NAME, 0); |
| } |
| |
| if (requestLine.getUri() instanceof SipUri) { |
| String scheme = ((SipUri) requestLine.getUri()).getScheme(); |
| if ("sips".equalsIgnoreCase(scheme)) { |
| SipUri sipUri = (SipUri) this.getContactHeader().getAddress().getURI(); |
| if (!sipUri.getScheme().equals("sips")) { |
| throw new ParseException("Scheme for contact should be sips:" + sipUri, 0); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Contact header is mandatory for a SIP INVITE request. |
| */ |
| if (this.getContactHeader() == null |
| && (this.getMethod().equals(Request.INVITE) |
| || this.getMethod().equals(Request.REFER) || this.getMethod().equals( |
| Request.SUBSCRIBE))) { |
| throw new ParseException("Contact Header is Mandatory for a SIP INVITE", 0); |
| } |
| |
| if (requestLine != null && requestLine.getMethod() != null |
| && getCSeq().getMethod() != null |
| && requestLine.getMethod().compareTo(getCSeq().getMethod()) != 0) { |
| throw new ParseException("CSEQ method mismatch with Request-Line ", 0); |
| |
| } |
| |
| } |
| |
| /** |
| * Set the default values in the request URI if necessary. |
| */ |
| protected void setDefaults() { |
| // The request line may be unparseable (set to null by the |
| // exception handler. |
| if (requestLine == null) |
| return; |
| String method = requestLine.getMethod(); |
| // The requestLine may be malformed! |
| if (method == null) |
| return; |
| GenericURI u = requestLine.getUri(); |
| if (u == null) |
| return; |
| if (method.compareTo(Request.REGISTER) == 0 || method.compareTo(Request.INVITE) == 0) { |
| if (u instanceof SipUri) { |
| SipUri sipUri = (SipUri) u; |
| sipUri.setUserParam(DEFAULT_USER); |
| try { |
| sipUri.setTransportParam(DEFAULT_TRANSPORT); |
| } catch (ParseException ex) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Patch up the request line as necessary. |
| */ |
| protected void setRequestLineDefaults() { |
| String method = requestLine.getMethod(); |
| if (method == null) { |
| CSeq cseq = (CSeq) this.getCSeq(); |
| if (cseq != null) { |
| method = getCannonicalName(cseq.getMethod()); |
| requestLine.setMethod(method); |
| } |
| } |
| } |
| |
| /** |
| * A conveniance function to access the Request URI. |
| * |
| * @return the requestURI if it exists. |
| */ |
| public javax.sip.address.URI getRequestURI() { |
| if (this.requestLine == null) |
| return null; |
| else |
| return (javax.sip.address.URI) this.requestLine.getUri(); |
| } |
| |
| /** |
| * Sets the RequestURI of Request. The Request-URI is a SIP or SIPS URI or a general URI. It |
| * indicates the user or service to which this request is being addressed. SIP elements MAY |
| * support Request-URIs with schemes other than "sip" and "sips", for example the "tel" URI |
| * scheme. SIP elements MAY translate non-SIP URIs using any mechanism at their disposal, |
| * resulting in SIP URI, SIPS URI, or some other scheme. |
| * |
| * @param uri the new Request URI of this request message |
| */ |
| public void setRequestURI(URI uri) { |
| if ( uri == null ) { |
| throw new NullPointerException("Null request URI"); |
| } |
| if (this.requestLine == null) { |
| this.requestLine = new RequestLine(); |
| } |
| this.requestLine.setUri((GenericURI) uri); |
| this.nullRequest = false; |
| } |
| |
| /** |
| * Set the method. |
| * |
| * @param method is the method to set. |
| * @throws IllegalArgumentException if the method is null |
| */ |
| public void setMethod(String method) { |
| if (method == null) |
| throw new IllegalArgumentException("null method"); |
| if (this.requestLine == null) { |
| this.requestLine = new RequestLine(); |
| } |
| |
| // Set to standard constants to speed up processing. |
| // this makes equals compares run much faster in the |
| // stack because then it is just identity comparision |
| |
| String meth = getCannonicalName(method); |
| this.requestLine.setMethod(meth); |
| |
| if (this.cSeqHeader != null) { |
| try { |
| this.cSeqHeader.setMethod(meth); |
| } catch (ParseException e) { |
| } |
| } |
| } |
| |
| /** |
| * Get the method from the request line. |
| * |
| * @return the method from the request line if the method exits and null if the request line |
| * or the method does not exist. |
| */ |
| public String getMethod() { |
| if (requestLine == null) |
| return null; |
| else |
| return requestLine.getMethod(); |
| } |
| |
| /** |
| * Encode the SIP Request as a string. |
| * |
| * @return an encoded String containing the encoded SIP Message. |
| */ |
| |
| public String encode() { |
| String retval; |
| if (requestLine != null) { |
| this.setRequestLineDefaults(); |
| retval = requestLine.encode() + super.encode(); |
| } else if (this.isNullRequest()) { |
| retval = "\r\n\r\n"; |
| } else { |
| retval = super.encode(); |
| } |
| return retval; |
| } |
| |
| /** |
| * Encode only the headers and not the content. |
| */ |
| public String encodeMessage() { |
| String retval; |
| if (requestLine != null) { |
| this.setRequestLineDefaults(); |
| retval = requestLine.encode() + super.encodeSIPHeaders(); |
| } else if (this.isNullRequest()) { |
| retval = "\r\n\r\n"; |
| } else |
| retval = super.encodeSIPHeaders(); |
| return retval; |
| |
| } |
| |
| /** |
| * ALias for encode above. |
| */ |
| public String toString() { |
| return this.encode(); |
| } |
| |
| /** |
| * Make a clone (deep copy) of this object. You can use this if you want to modify a request |
| * while preserving the original |
| * |
| * @return a deep copy of this object. |
| */ |
| |
| public Object clone() { |
| SIPRequest retval = (SIPRequest) super.clone(); |
| // Do not copy over the tx pointer -- this is only for internal |
| // tracking. |
| retval.transactionPointer = null; |
| if (this.requestLine != null) |
| retval.requestLine = (RequestLine) this.requestLine.clone(); |
| |
| return retval; |
| } |
| |
| /** |
| * Compare for equality. |
| * |
| * @param other object to compare ourselves with. |
| */ |
| public boolean equals(Object other) { |
| if (!this.getClass().equals(other.getClass())) |
| return false; |
| SIPRequest that = (SIPRequest) other; |
| |
| return requestLine.equals(that.requestLine) && super.equals(other); |
| } |
| |
| /** |
| * Get the message as a linked list of strings. Use this if you want to iterate through the |
| * message. |
| * |
| * @return a linked list containing the request line and headers encoded as strings. |
| */ |
| public LinkedList getMessageAsEncodedStrings() { |
| LinkedList retval = super.getMessageAsEncodedStrings(); |
| if (requestLine != null) { |
| this.setRequestLineDefaults(); |
| retval.addFirst(requestLine.encode()); |
| } |
| return retval; |
| |
| } |
| |
| /** |
| * Match with a template. You can use this if you want to match incoming messages with a |
| * pattern and do something when you find a match. This is useful for building filters/pattern |
| * matching responders etc. |
| * |
| * @param matchObj object to match ourselves with (null matches wildcard) |
| * |
| */ |
| public boolean match(Object matchObj) { |
| if (matchObj == null) |
| return true; |
| else if (!matchObj.getClass().equals(this.getClass())) |
| return false; |
| else if (matchObj == this) |
| return true; |
| SIPRequest that = (SIPRequest) matchObj; |
| RequestLine rline = that.requestLine; |
| if (this.requestLine == null && rline != null) |
| return false; |
| else if (this.requestLine == rline) |
| return super.match(matchObj); |
| return requestLine.match(that.requestLine) && super.match(matchObj); |
| |
| } |
| |
| /** |
| * Get a dialog identifier. Generates a string that can be used as a dialog identifier. |
| * |
| * @param isServer is set to true if this is the UAS and set to false if this is the UAC |
| */ |
| public String getDialogId(boolean isServer) { |
| CallID cid = (CallID) this.getCallId(); |
| StringBuffer retval = new StringBuffer(cid.getCallId()); |
| From from = (From) this.getFrom(); |
| To to = (To) this.getTo(); |
| if (!isServer) { |
| // retval.append(COLON).append(from.getUserAtHostPort()); |
| if (from.getTag() != null) { |
| retval.append(COLON); |
| retval.append(from.getTag()); |
| } |
| // retval.append(COLON).append(to.getUserAtHostPort()); |
| if (to.getTag() != null) { |
| retval.append(COLON); |
| retval.append(to.getTag()); |
| } |
| } else { |
| // retval.append(COLON).append(to.getUserAtHostPort()); |
| if (to.getTag() != null) { |
| retval.append(COLON); |
| retval.append(to.getTag()); |
| } |
| // retval.append(COLON).append(from.getUserAtHostPort()); |
| if (from.getTag() != null) { |
| retval.append(COLON); |
| retval.append(from.getTag()); |
| } |
| } |
| return retval.toString().toLowerCase(); |
| |
| } |
| |
| /** |
| * Get a dialog id given the remote tag. |
| */ |
| public String getDialogId(boolean isServer, String toTag) { |
| From from = (From) this.getFrom(); |
| CallID cid = (CallID) this.getCallId(); |
| StringBuffer retval = new StringBuffer(cid.getCallId()); |
| if (!isServer) { |
| // retval.append(COLON).append(from.getUserAtHostPort()); |
| if (from.getTag() != null) { |
| retval.append(COLON); |
| retval.append(from.getTag()); |
| } |
| // retval.append(COLON).append(to.getUserAtHostPort()); |
| if (toTag != null) { |
| retval.append(COLON); |
| retval.append(toTag); |
| } |
| } else { |
| // retval.append(COLON).append(to.getUserAtHostPort()); |
| if (toTag != null) { |
| retval.append(COLON); |
| retval.append(toTag); |
| } |
| // retval.append(COLON).append(from.getUserAtHostPort()); |
| if (from.getTag() != null) { |
| retval.append(COLON); |
| retval.append(from.getTag()); |
| } |
| } |
| return retval.toString().toLowerCase(); |
| } |
| |
| /** |
| * Encode this into a byte array. This is used when the body has been set as a binary array |
| * and you want to encode the body as a byte array for transmission. |
| * |
| * @return a byte array containing the SIPRequest encoded as a byte array. |
| */ |
| |
| public byte[] encodeAsBytes(String transport) { |
| if (this.isNullRequest()) { |
| // Encoding a null message for keepalive. |
| return "\r\n\r\n".getBytes(); |
| } else if ( this.requestLine == null ) { |
| return new byte[0]; |
| } |
| |
| byte[] rlbytes = null; |
| if (requestLine != null) { |
| try { |
| rlbytes = requestLine.encode().getBytes("UTF-8"); |
| } catch (UnsupportedEncodingException ex) { |
| InternalErrorHandler.handleException(ex); |
| } |
| } |
| byte[] superbytes = super.encodeAsBytes(transport); |
| byte[] retval = new byte[rlbytes.length + superbytes.length]; |
| System.arraycopy(rlbytes, 0, retval, 0, rlbytes.length); |
| System.arraycopy(superbytes, 0, retval, rlbytes.length, superbytes.length); |
| return retval; |
| } |
| |
| /** |
| * Creates a default SIPResponse message for this request. Note You must add the necessary |
| * tags to outgoing responses if need be. For efficiency, this method does not clone the |
| * incoming request. If you want to modify the outgoing response, be sure to clone the |
| * incoming request as the headers are shared and any modification to the headers of the |
| * outgoing response will result in a modification of the incoming request. Tag fields are |
| * just copied from the incoming request. Contact headers are removed from the incoming |
| * request. Added by Jeff Keyser. |
| * |
| * @param statusCode Status code for the response. Reason phrase is generated. |
| * |
| * @return A SIPResponse with the status and reason supplied, and a copy of all the original |
| * headers from this request. |
| */ |
| |
| public SIPResponse createResponse(int statusCode) { |
| |
| String reasonPhrase = SIPResponse.getReasonPhrase(statusCode); |
| return this.createResponse(statusCode, reasonPhrase); |
| |
| } |
| |
| /** |
| * Creates a default SIPResponse message for this request. Note You must add the necessary |
| * tags to outgoing responses if need be. For efficiency, this method does not clone the |
| * incoming request. If you want to modify the outgoing response, be sure to clone the |
| * incoming request as the headers are shared and any modification to the headers of the |
| * outgoing response will result in a modification of the incoming request. Tag fields are |
| * just copied from the incoming request. Contact headers are removed from the incoming |
| * request. Added by Jeff Keyser. Route headers are not added to the response. |
| * |
| * @param statusCode Status code for the response. |
| * @param reasonPhrase Reason phrase for this response. |
| * |
| * @return A SIPResponse with the status and reason supplied, and a copy of all the original |
| * headers from this request except the ones that are not supposed to be part of the |
| * response . |
| */ |
| |
| public SIPResponse createResponse(int statusCode, String reasonPhrase) { |
| SIPResponse newResponse; |
| Iterator headerIterator; |
| SIPHeader nextHeader; |
| |
| newResponse = new SIPResponse(); |
| try { |
| newResponse.setStatusCode(statusCode); |
| } catch (ParseException ex) { |
| throw new IllegalArgumentException("Bad code " + statusCode); |
| } |
| if (reasonPhrase != null) |
| newResponse.setReasonPhrase(reasonPhrase); |
| else |
| newResponse.setReasonPhrase(SIPResponse.getReasonPhrase(statusCode)); |
| headerIterator = getHeaders(); |
| while (headerIterator.hasNext()) { |
| nextHeader = (SIPHeader) headerIterator.next(); |
| if (nextHeader instanceof From |
| || nextHeader instanceof To |
| || nextHeader instanceof ViaList |
| || nextHeader instanceof CallID |
| || (nextHeader instanceof RecordRouteList && mustCopyRR(statusCode)) |
| || nextHeader instanceof CSeq |
| // We just copy TimeStamp for all headers (not just 100). |
| || nextHeader instanceof TimeStamp) { |
| |
| try { |
| |
| newResponse.attachHeader((SIPHeader) nextHeader.clone(), false); |
| } catch (SIPDuplicateHeaderException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| if (MessageFactoryImpl.getDefaultServerHeader() != null) { |
| newResponse.setHeader(MessageFactoryImpl.getDefaultServerHeader()); |
| |
| } |
| if (newResponse.getStatusCode() == 100) { |
| // Trying is never supposed to have the tag parameter set. |
| newResponse.getTo().removeParameter("tag"); |
| |
| } |
| ServerHeader server = MessageFactoryImpl.getDefaultServerHeader(); |
| if (server != null) { |
| newResponse.setHeader(server); |
| } |
| return newResponse; |
| } |
| |
| // Helper method for createResponse, to avoid copying Record-Route unless needed |
| private final boolean mustCopyRR( int code ) { |
| // Only for 1xx-2xx, not for 100 or errors |
| if ( code>100 && code<300 ) { |
| return isDialogCreating( this.getMethod() ) && getToTag() == null; |
| } else return false; |
| } |
| |
| /** |
| * Creates a default SIPResquest message that would cancel this request. Note that tag |
| * assignment and removal of is left to the caller (we use whatever tags are present in the |
| * original request). |
| * |
| * @return A CANCEL SIPRequest constructed according to RFC3261 section 9.1 |
| * |
| * @throws SipException |
| * @throws ParseException |
| */ |
| public SIPRequest createCancelRequest() throws SipException { |
| |
| // see RFC3261 9.1 |
| |
| // A CANCEL request SHOULD NOT be sent to cancel a request other than |
| // INVITE |
| |
| if (!this.getMethod().equals(Request.INVITE)) |
| throw new SipException("Attempt to create CANCEL for " + this.getMethod()); |
| |
| /* |
| * The following procedures are used to construct a CANCEL request. The Request-URI, |
| * Call-ID, To, the numeric part of CSeq, and From header fields in the CANCEL request |
| * MUST be identical to those in the request being cancelled, including tags. A CANCEL |
| * constructed by a client MUST have only a single Via header field value matching the top |
| * Via value in the request being cancelled. Using the same values for these header fields |
| * allows the CANCEL to be matched with the request it cancels (Section 9.2 indicates how |
| * such matching occurs). However, the method part of the CSeq header field MUST have a |
| * value of CANCEL. This allows it to be identified and processed as a transaction in its |
| * own right (See Section 17). |
| */ |
| SIPRequest cancel = new SIPRequest(); |
| cancel.setRequestLine((RequestLine) this.requestLine.clone()); |
| cancel.setMethod(Request.CANCEL); |
| cancel.setHeader((Header) this.callIdHeader.clone()); |
| cancel.setHeader((Header) this.toHeader.clone()); |
| cancel.setHeader((Header) cSeqHeader.clone()); |
| try { |
| cancel.getCSeq().setMethod(Request.CANCEL); |
| } catch (ParseException e) { |
| e.printStackTrace(); // should not happen |
| } |
| cancel.setHeader((Header) this.fromHeader.clone()); |
| |
| cancel.addFirst((Header) this.getTopmostVia().clone()); |
| cancel.setHeader((Header) this.maxForwardsHeader.clone()); |
| |
| /* |
| * If the request being cancelled contains a Route header field, the CANCEL request MUST |
| * include that Route header field's values. |
| */ |
| if (this.getRouteHeaders() != null) { |
| cancel.setHeader((SIPHeaderList< ? >) this.getRouteHeaders().clone()); |
| } |
| if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { |
| cancel.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); |
| |
| } |
| return cancel; |
| } |
| |
| /** |
| * Creates a default ACK SIPRequest message for this original request. Note that the |
| * defaultACK SIPRequest does not include the content of the original SIPRequest. If |
| * responseToHeader is null then the toHeader of this request is used to construct the ACK. |
| * Note that tag fields are just copied from the original SIP Request. Added by Jeff Keyser. |
| * |
| * @param responseToHeader To header to use for this request. |
| * |
| * @return A SIPRequest with an ACK method. |
| */ |
| public SIPRequest createAckRequest(To responseToHeader) { |
| SIPRequest newRequest; |
| Iterator headerIterator; |
| SIPHeader nextHeader; |
| |
| newRequest = new SIPRequest(); |
| newRequest.setRequestLine((RequestLine) this.requestLine.clone()); |
| newRequest.setMethod(Request.ACK); |
| headerIterator = getHeaders(); |
| while (headerIterator.hasNext()) { |
| nextHeader = (SIPHeader) headerIterator.next(); |
| if (nextHeader instanceof RouteList) { |
| // Ack and cancel do not get ROUTE headers. |
| // Route header for ACK is assigned by the |
| // Dialog if necessary. |
| continue; |
| } else if (nextHeader instanceof ProxyAuthorization) { |
| // Remove proxy auth header. |
| // Assigned by the Dialog if necessary. |
| continue; |
| } else if (nextHeader instanceof ContentLength) { |
| // Adding content is responsibility of user. |
| nextHeader = (SIPHeader) nextHeader.clone(); |
| try { |
| ((ContentLength) nextHeader).setContentLength(0); |
| } catch (InvalidArgumentException e) { |
| } |
| } else if (nextHeader instanceof ContentType) { |
| // Content type header is removed since |
| // content length is 0. |
| continue; |
| } else if (nextHeader instanceof CSeq) { |
| // The CSeq header field in the |
| // ACK MUST contain the same value for the |
| // sequence number as was present in the |
| // original request, but the method parameter |
| // MUST be equal to "ACK". |
| CSeq cseq = (CSeq) nextHeader.clone(); |
| try { |
| cseq.setMethod(Request.ACK); |
| } catch (ParseException e) { |
| } |
| nextHeader = cseq; |
| } else if (nextHeader instanceof To) { |
| if (responseToHeader != null) { |
| nextHeader = responseToHeader; |
| } else { |
| nextHeader = (SIPHeader) nextHeader.clone(); |
| } |
| } else if (nextHeader instanceof ContactList || nextHeader instanceof Expires) { |
| // CONTACT header does not apply for ACK requests. |
| continue; |
| } else if (nextHeader instanceof ViaList) { |
| // Bug reported by Gianluca Martinello |
| // The ACK MUST contain a single Via header field, |
| // and this MUST be equal to the top Via header |
| // field of the original |
| // request. |
| |
| nextHeader = (SIPHeader) ((ViaList) nextHeader).getFirst().clone(); |
| } else { |
| nextHeader = (SIPHeader) nextHeader.clone(); |
| } |
| |
| try { |
| newRequest.attachHeader(nextHeader, false); |
| } catch (SIPDuplicateHeaderException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { |
| newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); |
| |
| } |
| return newRequest; |
| } |
| |
| /** |
| * Creates an ACK for non-2xx responses according to RFC3261 17.1.1.3 |
| * |
| * @return A SIPRequest with an ACK method. |
| * @throws SipException |
| * @throws NullPointerException |
| * @throws ParseException |
| * |
| * @author jvb |
| */ |
| public final SIPRequest createErrorAck(To responseToHeader) throws SipException, |
| ParseException { |
| |
| /* |
| * The ACK request constructed by the client transaction MUST contain values for the |
| * Call-ID, From, and Request-URI that are equal to the values of those header fields in |
| * the request passed to the transport by the client transaction (call this the "original |
| * request"). The To header field in the ACK MUST equal the To header field in the |
| * response being acknowledged, and therefore will usually differ from the To header field |
| * in the original request by the addition of the tag parameter. The ACK MUST contain a |
| * single Via header field, and this MUST be equal to the top Via header field of the |
| * original request. The CSeq header field in the ACK MUST contain the same value for the |
| * sequence number as was present in the original request, but the method parameter MUST |
| * be equal to "ACK". |
| */ |
| SIPRequest newRequest = new SIPRequest(); |
| newRequest.setRequestLine((RequestLine) this.requestLine.clone()); |
| newRequest.setMethod(Request.ACK); |
| newRequest.setHeader((Header) this.callIdHeader.clone()); |
| newRequest.setHeader((Header) this.maxForwardsHeader.clone()); // ISSUE |
| // 130 |
| // fix |
| newRequest.setHeader((Header) this.fromHeader.clone()); |
| newRequest.setHeader((Header) responseToHeader.clone()); |
| newRequest.addFirst((Header) this.getTopmostVia().clone()); |
| newRequest.setHeader((Header) cSeqHeader.clone()); |
| newRequest.getCSeq().setMethod(Request.ACK); |
| |
| /* |
| * If the INVITE request whose response is being acknowledged had Route header fields, |
| * those header fields MUST appear in the ACK. This is to ensure that the ACK can be |
| * routed properly through any downstream stateless proxies. |
| */ |
| if (this.getRouteHeaders() != null) { |
| newRequest.setHeader((SIPHeaderList) this.getRouteHeaders().clone()); |
| } |
| if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { |
| newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); |
| |
| } |
| return newRequest; |
| } |
| |
| /** |
| * Create a new default SIPRequest from the original request. Warning: the newly created |
| * SIPRequest, shares the headers of this request but we generate any new headers that we need |
| * to modify so the original request is umodified. However, if you modify the shared headers |
| * after this request is created, then the newly created request will also be modified. If you |
| * want to modify the original request without affecting the returned Request make sure you |
| * clone it before calling this method. |
| * |
| * Only required headers are copied. |
| * <ul> |
| * <li> Contact headers are not included in the newly created request. Setting the appropriate |
| * sequence number is the responsibility of the caller. </li> |
| * <li> RouteList is not copied for ACK and CANCEL </li> |
| * <li> Note that we DO NOT copy the body of the argument into the returned header. We do not |
| * copy the content type header from the original request either. These have to be added |
| * seperately and the content length has to be correctly set if necessary the content length |
| * is set to 0 in the returned header. </li> |
| * <li>Contact List is not copied from the original request.</li> |
| * <li>RecordRoute List is not included from original request. </li> |
| * <li>Via header is not included from the original request. </li> |
| * </ul> |
| * |
| * @param requestLine is the new request line. |
| * |
| * @param switchHeaders is a boolean flag that causes to and from headers to switch (set this |
| * to true if you are the server of the transaction and are generating a BYE request). |
| * If the headers are switched, we generate new From and To headers otherwise we just |
| * use the incoming headers. |
| * |
| * @return a new Default SIP Request which has the requestLine specified. |
| * |
| */ |
| public SIPRequest createSIPRequest(RequestLine requestLine, boolean switchHeaders) { |
| SIPRequest newRequest = new SIPRequest(); |
| newRequest.requestLine = requestLine; |
| Iterator<SIPHeader> headerIterator = this.getHeaders(); |
| while (headerIterator.hasNext()) { |
| SIPHeader nextHeader = (SIPHeader) headerIterator.next(); |
| // For BYE and cancel set the CSeq header to the |
| // appropriate method. |
| if (nextHeader instanceof CSeq) { |
| CSeq newCseq = (CSeq) nextHeader.clone(); |
| nextHeader = newCseq; |
| try { |
| newCseq.setMethod(requestLine.getMethod()); |
| } catch (ParseException e) { |
| } |
| } else if (nextHeader instanceof ViaList) { |
| Via via = (Via) (((ViaList) nextHeader).getFirst().clone()); |
| via.removeParameter("branch"); |
| nextHeader = via; |
| // Cancel and ACK preserve the branch ID. |
| } else if (nextHeader instanceof To) { |
| To to = (To) nextHeader; |
| if (switchHeaders) { |
| nextHeader = new From(to); |
| ((From) nextHeader).removeTag(); |
| } else { |
| nextHeader = (SIPHeader) to.clone(); |
| ((To) nextHeader).removeTag(); |
| } |
| } else if (nextHeader instanceof From) { |
| From from = (From) nextHeader; |
| if (switchHeaders) { |
| nextHeader = new To(from); |
| ((To) nextHeader).removeTag(); |
| } else { |
| nextHeader = (SIPHeader) from.clone(); |
| ((From) nextHeader).removeTag(); |
| } |
| } else if (nextHeader instanceof ContentLength) { |
| ContentLength cl = (ContentLength) nextHeader.clone(); |
| try { |
| cl.setContentLength(0); |
| } catch (InvalidArgumentException e) { |
| } |
| nextHeader = cl; |
| } else if (!(nextHeader instanceof CallID) && !(nextHeader instanceof MaxForwards)) { |
| // Route is kept by dialog. |
| // RR is added by the caller. |
| // Contact is added by the Caller |
| // Any extension headers must be added |
| // by the caller. |
| continue; |
| } |
| try { |
| newRequest.attachHeader(nextHeader, false); |
| } catch (SIPDuplicateHeaderException e) { |
| e.printStackTrace(); |
| } |
| } |
| if (MessageFactoryImpl.getDefaultUserAgentHeader() != null) { |
| newRequest.setHeader(MessageFactoryImpl.getDefaultUserAgentHeader()); |
| |
| } |
| return newRequest; |
| |
| } |
| |
| /** |
| * Create a BYE request from this request. |
| * |
| * @param switchHeaders is a boolean flag that causes from and isServerTransaction to headers |
| * to be swapped. Set this to true if you are the server of the dialog and are |
| * generating a BYE request for the dialog. |
| * @return a new default BYE request. |
| */ |
| public SIPRequest createBYERequest(boolean switchHeaders) { |
| RequestLine requestLine = (RequestLine) this.requestLine.clone(); |
| requestLine.setMethod("BYE"); |
| return this.createSIPRequest(requestLine, switchHeaders); |
| } |
| |
| /** |
| * Create an ACK request from this request. This is suitable for generating an ACK for an |
| * INVITE client transaction. |
| * |
| * @return an ACK request that is generated from this request. |
| */ |
| public SIPRequest createACKRequest() { |
| RequestLine requestLine = (RequestLine) this.requestLine.clone(); |
| requestLine.setMethod(Request.ACK); |
| return this.createSIPRequest(requestLine, false); |
| } |
| |
| /** |
| * Get the host from the topmost via header. |
| * |
| * @return the string representation of the host from the topmost via header. |
| */ |
| public String getViaHost() { |
| Via via = (Via) this.getViaHeaders().getFirst(); |
| return via.getHost(); |
| |
| } |
| |
| /** |
| * Get the port from the topmost via header. |
| * |
| * @return the port from the topmost via header (5060 if there is no port indicated). |
| */ |
| public int getViaPort() { |
| Via via = (Via) this.getViaHeaders().getFirst(); |
| if (via.hasPort()) |
| return via.getPort(); |
| else |
| return 5060; |
| } |
| |
| /** |
| * Get the first line encoded. |
| * |
| * @return a string containing the encoded request line. |
| */ |
| public String getFirstLine() { |
| if (requestLine == null) |
| return null; |
| else |
| return this.requestLine.encode(); |
| } |
| |
| /** |
| * Set the sip version. |
| * |
| * @param sipVersion the sip version to set. |
| */ |
| public void setSIPVersion(String sipVersion) throws ParseException { |
| if (sipVersion == null || !sipVersion.equalsIgnoreCase("SIP/2.0")) |
| throw new ParseException("sipVersion", 0); |
| this.requestLine.setSipVersion(sipVersion); |
| } |
| |
| /** |
| * Get the SIP version. |
| * |
| * @return the SIP version from the request line. |
| */ |
| public String getSIPVersion() { |
| return this.requestLine.getSipVersion(); |
| } |
| |
| /** |
| * Book keeping method to return the current tx for the request if one exists. |
| * |
| * @return the assigned tx. |
| */ |
| public Object getTransaction() { |
| // Return an opaque pointer to the transaction object. |
| // This is for consistency checking and quick lookup. |
| return this.transactionPointer; |
| } |
| |
| /** |
| * Book keeping field to set the current tx for the request. |
| * |
| * @param transaction |
| */ |
| public void setTransaction(Object transaction) { |
| this.transactionPointer = transaction; |
| } |
| |
| /** |
| * Book keeping method to get the messasge channel for the request. |
| * |
| * @return the message channel for the request. |
| */ |
| |
| public Object getMessageChannel() { |
| // return opaque ptr to the message chanel on |
| // which the message was recieved. For consistency |
| // checking and lookup. |
| return this.messageChannel; |
| } |
| |
| /** |
| * Set the message channel for the request ( bookkeeping field ). |
| * |
| * @param messageChannel |
| */ |
| |
| public void setMessageChannel(Object messageChannel) { |
| this.messageChannel = messageChannel; |
| } |
| |
| /** |
| * Generates an Id for checking potentially merged requests. |
| * |
| * @return String to check for merged requests |
| */ |
| public String getMergeId() { |
| /* |
| * generate an identifier from the From tag, Call-ID, and CSeq |
| */ |
| String fromTag = this.getFromTag(); |
| String cseq = this.cSeqHeader.toString(); |
| String callId = this.callIdHeader.getCallId(); |
| /* NOTE : The RFC does NOT specify you need to include a Request URI |
| * This is added here for the case of Back to Back User Agents. |
| */ |
| String requestUri = this.getRequestURI().toString(); |
| |
| if (fromTag != null) { |
| return new StringBuffer().append(requestUri).append(":").append(fromTag).append(":").append(cseq).append(":") |
| .append(callId).toString(); |
| } else |
| return null; |
| |
| } |
| |
| /** |
| * @param inviteTransaction the inviteTransaction to set |
| */ |
| public void setInviteTransaction(Object inviteTransaction) { |
| this.inviteTransaction = inviteTransaction; |
| } |
| |
| /** |
| * @return the inviteTransaction |
| */ |
| public Object getInviteTransaction() { |
| return inviteTransaction; |
| } |
| |
| |
| |
| |
| |
| } |