summaryrefslogtreecommitdiffstats
path: root/java/gov/nist/javax/sip/message/SIPRequest.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/gov/nist/javax/sip/message/SIPRequest.java')
-rw-r--r--java/gov/nist/javax/sip/message/SIPRequest.java1207
1 files changed, 1207 insertions, 0 deletions
diff --git a/java/gov/nist/javax/sip/message/SIPRequest.java b/java/gov/nist/javax/sip/message/SIPRequest.java
new file mode 100644
index 0000000..3136383
--- /dev/null
+++ b/java/gov/nist/javax/sip/message/SIPRequest.java
@@ -0,0 +1,1207 @@
+/*
+ * 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);
+ }
+ if (getMaxForwards() == null) {
+ throw new ParseException(prefix + MaxForwardsHeader.NAME, 0);
+ }
+
+ 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;
+ }
+
+
+
+
+
+}