diff options
Diffstat (limited to 'java/gov/nist/javax/sip/message/SIPRequest.java')
-rw-r--r-- | java/gov/nist/javax/sip/message/SIPRequest.java | 1207 |
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; + } + + + + + +} |