/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.servlet.sip; import gov.nist.javax.sip.header.extensions.ReferredByHeader; import gov.nist.javax.sip.header.extensions.SessionExpiresHeader; import gov.nist.javax.sip.header.ims.PAssertedIdentityHeader; import gov.nist.javax.sip.header.ims.PathHeader; import gov.nist.javax.sip.message.SIPMessage; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.sip.ListeningPoint; import javax.sip.address.SipURI; import javax.sip.header.AcceptEncodingHeader; import javax.sip.header.AcceptHeader; import javax.sip.header.AlertInfoHeader; import javax.sip.header.AllowEventsHeader; import javax.sip.header.AllowHeader; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.CallInfoHeader; import javax.sip.header.ContactHeader; import javax.sip.header.ContentDispositionHeader; import javax.sip.header.ContentEncodingHeader; import javax.sip.header.ContentLengthHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.ErrorInfoHeader; import javax.sip.header.EventHeader; import javax.sip.header.FromHeader; import javax.sip.header.RAckHeader; import javax.sip.header.RSeqHeader; import javax.sip.header.RecordRouteHeader; import javax.sip.header.ReferToHeader; import javax.sip.header.ReplyToHeader; import javax.sip.header.RetryAfterHeader; import javax.sip.header.RouteHeader; import javax.sip.header.SubjectHeader; import javax.sip.header.SupportedHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Message; import javax.sip.message.Request; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.core.ExtendedListeningPoint; import org.mobicents.servlet.sip.core.SipNetworkInterfaceManager; import org.mobicents.servlet.sip.message.SipFactoryImpl.NamesComparator; /** * * Various helpful utilities to map jain sip abstractions. * * @author mranga * @author Jean Deruelle */ public class JainSipUtils { /** * The maximum int value that could correspond to a port nubmer. */ public static final int MAX_PORT_NUMBER = 65535; /** * The minimum int value that could correspond to a port nubmer bindable * by the SIP Communicator. */ public static final int MIN_PORT_NUMBER = 1024; private static transient Logger logger = Logger.getLogger(JainSipUtils.class); public static final String GLOBAL_IPADDRESS = "0.0.0.0"; public static final TreeSet<String> dialogCreatingMethods = new TreeSet<String>( new NamesComparator()); public static final TreeSet<String> dialogTerminatingMethods = new TreeSet<String>( new NamesComparator()); static { dialogCreatingMethods.add(Request.INVITE); dialogCreatingMethods.add(Request.SUBSCRIBE); dialogCreatingMethods.add(Request.REFER); dialogTerminatingMethods.add(Request.CANCEL); dialogTerminatingMethods.add(Request.BYE); } public static final String INITIAL_REMOTE_ADDR_HEADER_NAME = "MSS_Initial_Remote_Addr"; public static final String INITIAL_REMOTE_PORT_HEADER_NAME = "MSS_Initial_Remote_Port"; public static final String INITIAL_REMOTE_TRANSPORT_HEADER_NAME = "MSS_Initial_Remote_Transport"; /** * List of headers that ARE system at all times */ public static final Set<String> systemHeaders = new HashSet<String>(); static { systemHeaders.add(FromHeader.NAME); systemHeaders.add(ToHeader.NAME); systemHeaders.add(CallIdHeader.NAME); systemHeaders.add(CSeqHeader.NAME); systemHeaders.add(ViaHeader.NAME); systemHeaders.add(RouteHeader.NAME); systemHeaders.add(RecordRouteHeader.NAME); systemHeaders.add(PathHeader.NAME); // This is system in messages other than REGISTER!!! ContactHeader.NAME // Contact is a system header field in messages other than REGISTER // requests and responses, 3xx and 485 responses, and 200/OPTIONS // responses. Additionally, for containers implementing the reliable // provisional responses extension, RAck and RSeq are considered system // headers also. systemHeaders.add(RSeqHeader.NAME); systemHeaders.add(RAckHeader.NAME); //custom header used by Mobicents Sip Servlets and not allowed to be overriden by apps systemHeaders.add(INITIAL_REMOTE_ADDR_HEADER_NAME); systemHeaders.add(INITIAL_REMOTE_PORT_HEADER_NAME); systemHeaders.add(INITIAL_REMOTE_TRANSPORT_HEADER_NAME); } public static final Set<String> addressHeadersNames = new HashSet<String>(); static { // Section 4.1 The baseline SIP specification defines the following set of header // fields that conform to this grammar: From, To, Contact, Route, // Record-Route, Reply-To, Alert-Info, Call-Info, and Error-Info // The SipServletMessage interface defines a set of methods which operate // on any address header field (see section 5.4.1 Parameterable and Address Header Fields ). // This includes the RFC 3261 defined header fields listed above as well as extension headers // such as Refer-To [refer] and P-Asserted-Identity [privacy]. addressHeadersNames.add(FromHeader.NAME); addressHeadersNames.add(ToHeader.NAME); addressHeadersNames.add(ContactHeader.NAME); addressHeadersNames.add(RouteHeader.NAME); addressHeadersNames.add(RecordRouteHeader.NAME); addressHeadersNames.add(ReplyToHeader.NAME); addressHeadersNames.add(AlertInfoHeader.NAME); addressHeadersNames.add(CallInfoHeader.NAME); addressHeadersNames.add(ErrorInfoHeader.NAME); addressHeadersNames.add(ReferToHeader.NAME); addressHeadersNames.add(PAssertedIdentityHeader.NAME); } public static final Set<String> parameterableHeadersNames = new HashSet<String>(); static { // All of the Address header fields are Parameterable, including Contact, From, To, Route, Record-Route, and Reply-To. // In addition, the header fields Accept, Accept-Encoding, Alert-Info, // Call-Info, Content-Disposition, Content-Type, Error-Info, Retry-After and Via are also Parameterable. parameterableHeadersNames.add(FromHeader.NAME); parameterableHeadersNames.add(ToHeader.NAME); parameterableHeadersNames.add(ContactHeader.NAME); parameterableHeadersNames.add(RouteHeader.NAME); parameterableHeadersNames.add(RecordRouteHeader.NAME); parameterableHeadersNames.add(ReplyToHeader.NAME); parameterableHeadersNames.add(AcceptHeader.NAME); parameterableHeadersNames.add(AcceptEncodingHeader.NAME); parameterableHeadersNames.add(AlertInfoHeader.NAME); parameterableHeadersNames.add(CallInfoHeader.NAME); parameterableHeadersNames.add(ContentDispositionHeader.NAME); parameterableHeadersNames.add(ContentTypeHeader.NAME); parameterableHeadersNames.add(ErrorInfoHeader.NAME); parameterableHeadersNames.add(RetryAfterHeader.NAME); parameterableHeadersNames.add(ViaHeader.NAME); } public static final Map<String, String> headerCompact2FullNamesMappings = new HashMap<String, String>(); { // http://www.iana.org/assignments/sip-parameters // Header Name compact Reference // ----------------- ------- --------- // Call-ID i [RFC3261] // From f [RFC3261] // To t [RFC3261] // Via v [RFC3261] // =========== NON SYSTEM HEADERS ======== // Contact m [RFC3261] <-- Possibly system header // Accept-Contact a [RFC3841] // Allow-Events u [RFC3265] // Content-Encoding e [RFC3261] // Content-Length l [RFC3261] // Content-Type c [RFC3261] // Event o [RFC3265] // Identity y [RFC4474] // Identity-Info n [RFC4474] // Refer-To r [RFC3515] // Referred-By b [RFC3892] // Reject-Contact j [RFC3841] // Request-Disposition d [RFC3841] // Session-Expires x [RFC4028] // Subject s [RFC3261] // Supported k [RFC3261] headerCompact2FullNamesMappings.put("i", CallIdHeader.NAME); headerCompact2FullNamesMappings.put("f", FromHeader.NAME); headerCompact2FullNamesMappings.put("t", ToHeader.NAME); headerCompact2FullNamesMappings.put("v", ViaHeader.NAME); headerCompact2FullNamesMappings.put("m", ContactHeader.NAME); // headerCompact2FullNamesMappings.put("a",); // Where is this header? headerCompact2FullNamesMappings.put("u", AllowEventsHeader.NAME); headerCompact2FullNamesMappings.put("e", ContentEncodingHeader.NAME); headerCompact2FullNamesMappings.put("l", ContentLengthHeader.NAME); headerCompact2FullNamesMappings.put("c", ContentTypeHeader.NAME); headerCompact2FullNamesMappings.put("o", EventHeader.NAME); // headerCompact2FullNamesMappings.put("y", IdentityHeader); // Where is // sthis header? // headerCompact2FullNamesMappings.put("n",IdentityInfoHeader ); headerCompact2FullNamesMappings.put("r", ReferToHeader.NAME); headerCompact2FullNamesMappings.put("b", ReferredByHeader.NAME); // headerCompact2FullNamesMappings.put("j", RejectContactHeader); headerCompact2FullNamesMappings.put("d", ContentDispositionHeader.NAME); headerCompact2FullNamesMappings.put("x", SessionExpiresHeader.NAME); headerCompact2FullNamesMappings.put("s", SubjectHeader.NAME); headerCompact2FullNamesMappings.put("k", SupportedHeader.NAME); } public static final Map<String, String> headerFull2CompactNamesMappings = new HashMap<String, String>(); static { // http://www.iana.org/assignments/sip-parameters // Header Name compact Reference // ----------------- ------- --------- // Call-ID i [RFC3261] // From f [RFC3261] // To t [RFC3261] // Via v [RFC3261] // =========== NON SYSTEM HEADERS ======== // Contact m [RFC3261] <-- Possibly system header // Accept-Contact a [RFC3841] // Allow-Events u [RFC3265] // Content-Encoding e [RFC3261] // Content-Length l [RFC3261] // Content-Type c [RFC3261] // Event o [RFC3265] // Identity y [RFC4474] // Identity-Info n [RFC4474] // Refer-To r [RFC3515] // Referred-By b [RFC3892] // Reject-Contact j [RFC3841] // Request-Disposition d [RFC3841] // Session-Expires x [RFC4028] // Subject s [RFC3261] // Supported k [RFC3261] headerFull2CompactNamesMappings.put(CallIdHeader.NAME, "i"); headerFull2CompactNamesMappings.put(FromHeader.NAME, "f"); headerFull2CompactNamesMappings.put(ToHeader.NAME, "t"); headerFull2CompactNamesMappings.put(ViaHeader.NAME, "v"); headerFull2CompactNamesMappings.put(ContactHeader.NAME, "m"); // headerFull2CompactNamesMappings.put(,"a"); // Where is this header? headerFull2CompactNamesMappings.put(AllowEventsHeader.NAME, "u"); headerFull2CompactNamesMappings.put(ContentEncodingHeader.NAME, "e"); headerFull2CompactNamesMappings.put(ContentLengthHeader.NAME, "l"); headerFull2CompactNamesMappings.put(ContentTypeHeader.NAME, "c"); headerFull2CompactNamesMappings.put(EventHeader.NAME, "o"); // headerCompact2FullNamesMappings.put(IdentityHeader,"y"); // Where is // sthis header? // headerCompact2FullNamesMappings.put(IdentityInfoHeader ,"n"); headerFull2CompactNamesMappings.put(ReferToHeader.NAME, "r"); // headerCompact2FullNamesMappings.put(ReferedByHeader,"b"); // headerCompact2FullNamesMappings.put(RejectContactHeader,"j"); headerFull2CompactNamesMappings.put(ContentDispositionHeader.NAME, "d"); // headerCompact2FullNamesMappings.put(SessionExpiresHeader,"x"); headerFull2CompactNamesMappings.put(SubjectHeader.NAME, "s"); headerFull2CompactNamesMappings.put(SupportedHeader.NAME, "k"); } public static final Set<String> ianaAllowedContentTypes = new HashSet<String>(); static { // All of the Address header fields are Parameterable, including Contact, From, To, Route, Record-Route, and Reply-To. // In addition, the header fields Accept, Accept-Encoding, Alert-Info, // Call-Info, Content-Disposition, Content-Type, Error-Info, Retry-After and Via are also Parameterable. ianaAllowedContentTypes.add("application"); ianaAllowedContentTypes.add("audio"); ianaAllowedContentTypes.add("example"); ianaAllowedContentTypes.add("image"); ianaAllowedContentTypes.add("message"); ianaAllowedContentTypes.add("model"); ianaAllowedContentTypes.add("multipart"); ianaAllowedContentTypes.add("text"); ianaAllowedContentTypes.add("video"); } // we don't have any other choice as to maintain a static list of multi value headers // because checking for , for the values as a delimiter won't work for WWW-Authenticate header which is not a multivalue header // but contains multiple , public static final Set<String> multiValueHeaders = new HashSet<String>(); static { multiValueHeaders.add(AllowHeader.NAME); } private static final String[] allowedAddressSchemes = {"sip","sips","http","https","tel","tels","mailto"}; public static final int MAX_FORWARD_HEADER_VALUE = 70; // never instantiate a utility class : Enforce noninstantiability with private constructor private JainSipUtils() { throw new AssertionError(); } // RFC 1918 address spaces public static int getAddressOutboundness(String address) { if(address.startsWith("127.0")) return 0; if(address.startsWith("192.168")) return 1; if(address.startsWith("10.")) return 2; if(address.startsWith("172.16") || address.startsWith("172.17") || address.startsWith("172.18") || address.startsWith("172.19") || address.startsWith("172.20") || address.startsWith("172.21") || address.startsWith("172.22") || address.startsWith("172.23") || address.startsWith("172.24") || address.startsWith("172.25") || address.startsWith("172.26") || address.startsWith("172.27") || address.startsWith("172.28") || address.startsWith("172.29") || address.startsWith("172.30") || address.startsWith("172.31")) return 3; if(address.indexOf(".")>0) return 4; // match IPv4 addresses heuristically return -1; // matches IPv6 or something malformed; } public static String getMostOutboundAddress(List<String> addresses) { // getIpAddresses() returns [0:0:0:0:0:0:0:1, 127.0.0.1, 2001:0:d5c7:a2ca:3065:1735:3f57:fe71, fe80:0:0:0:3065:1735:3f57:fe71%15, 192.168.1.142, fe80:0:0:0:0:5efe:c0a8:18e%21] // IPv6 addresses are not good for default value String bestAddr = "127.0.0.1"; // The default is something completely fails int bestAddrOutboundness = -2; for(String address:addresses) { int addrOutboundness = getAddressOutboundness(address); if(addrOutboundness>bestAddrOutboundness) { bestAddr = address; bestAddrOutboundness = addrOutboundness; } } return bestAddr; } /** * * @param sipNetworkInterfaceManager * @param transport * @param branch * @return */ public static ViaHeader createViaHeader( SipNetworkInterfaceManager sipNetworkInterfaceManager, Request request, String branch) { String transport = findTransport(request); ExtendedListeningPoint listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false); boolean usePublicAddress = findUsePublicAddress( sipNetworkInterfaceManager, request, listeningPoint); return listeningPoint.createViaHeader(branch, usePublicAddress); } /** * * @param sipNetworkInterfaceManager * @param transport * @return */ public static ContactHeader createContactHeader(SipNetworkInterfaceManager sipNetworkInterfaceManager, Request request, String displayName) { String transport = findTransport(request); ExtendedListeningPoint listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false); boolean usePublicAddress = findUsePublicAddress( sipNetworkInterfaceManager, request, listeningPoint); return listeningPoint.createContactHeader(displayName, usePublicAddress); } /** * * @param sipProviders * @param transport * @return */ public static javax.sip.address.SipURI createRecordRouteURI(SipNetworkInterfaceManager sipNetworkInterfaceManager, Message message) { String transport = findTransport(message); ExtendedListeningPoint listeningPoint = sipNetworkInterfaceManager.findMatchingListeningPoint(transport, false); boolean usePublicAddress = findUsePublicAddress( sipNetworkInterfaceManager, message, listeningPoint); return listeningPoint.createRecordRouteURI(usePublicAddress); } /** * * @param sipNetworkInterfaceManager * @param request * @param listeningPoint * @return */ public static boolean findUsePublicAddress( SipNetworkInterfaceManager sipNetworkInterfaceManager, Message message, ExtendedListeningPoint listeningPoint) { boolean usePublicAddress = false; if(listeningPoint.isUseStaticAddress()) { usePublicAddress = true; } else if(listeningPoint.getGlobalIpAddress() != null) { usePublicAddress = sipNetworkInterfaceManager.findUsePublicAddress(message); } return usePublicAddress; } /** * * @param request * @return */ public static String findTransport(Message message) { if(message == null) { return ListeningPoint.UDP; } // check if the transport is present in the message application data for maximizing perf String transport = (String)((SIPMessage)message).getApplicationData(); if(transport != null) { return transport; } //if the request uri doesn't have any param, the request can still be on TCP so we check the topmost via header ViaHeader topmostViaHeader = (ViaHeader) message.getHeader(ViaHeader.NAME); if(topmostViaHeader != null) { String viaTransport = topmostViaHeader.getTransport(); if(viaTransport != null && viaTransport.length() > 0) { transport = viaTransport; } } if(transport == null && message instanceof Request) { transport = ListeningPoint.UDP; Request request = (Request)message; if(request.getRequestURI() instanceof SipURI) { String transportParam = ((javax.sip.address.SipURI) request .getRequestURI()).getTransportParam(); if (transportParam != null && transportParam.equalsIgnoreCase(ListeningPoint.TLS)) { transport = ListeningPoint.TLS; } //Fix by Filip Olsson for Issue 112 else if ((transportParam != null && transportParam.equalsIgnoreCase(ListeningPoint.TCP)) || request.getContentLength().getContentLength() > 4096) { transport = ListeningPoint.TCP; } } } // storing the transport is present in the message application data for maximizing perf // in speeding up the retrieval later on ((SIPMessage)message).setApplicationData(transport); return transport; } public static boolean checkScheme(String address) { for(String scheme:allowedAddressSchemes) { int start = address.indexOf("<"); if(start >= 0) { int end = address.indexOf(">"); address = address.substring(start + 1, end); } if(scheme.equalsIgnoreCase(address.substring(0, scheme.length()))) return true; } return false; } }