| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.catalina.mapper; |
| |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Host; |
| import org.apache.catalina.WebResource; |
| import org.apache.catalina.WebResourceRoot; |
| import org.apache.catalina.Wrapper; |
| import org.apache.tomcat.util.buf.Ascii; |
| import org.apache.tomcat.util.buf.CharChunk; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * Mapper, which implements the servlet API mapping rules (which are derived |
| * from the HTTP rules). |
| * |
| * @author Remy Maucherat |
| */ |
| public final class Mapper { |
| |
| |
| private static final org.apache.juli.logging.Log log = |
| org.apache.juli.logging.LogFactory.getLog(Mapper.class); |
| |
| protected static final StringManager sm = |
| StringManager.getManager(Mapper.class.getPackage().getName()); |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * Array containing the virtual hosts definitions. |
| */ |
| protected MappedHost[] hosts = new MappedHost[0]; |
| |
| |
| /** |
| * Default host name. |
| */ |
| protected String defaultHostName = null; |
| |
| |
| /** |
| * Mapping from Context object to Context version to support |
| * RequestDispatcher mappings. |
| */ |
| protected Map<Context, ContextVersion> contextObjectToContextVersionMap = |
| new ConcurrentHashMap<>(); |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| /** |
| * Set default host. |
| * |
| * @param defaultHostName Default host name |
| */ |
| public void setDefaultHostName(String defaultHostName) { |
| this.defaultHostName = defaultHostName; |
| } |
| |
| /** |
| * Add a new host to the mapper. |
| * |
| * @param name Virtual host name |
| * @param host Host object |
| */ |
| public synchronized void addHost(String name, String[] aliases, |
| Host host) { |
| MappedHost[] newHosts = new MappedHost[hosts.length + 1]; |
| MappedHost newHost = new MappedHost(); |
| ContextList contextList = new ContextList(); |
| newHost.name = name; |
| newHost.contextList = contextList; |
| newHost.object = host; |
| if (insertMap(hosts, newHosts, newHost)) { |
| hosts = newHosts; |
| } |
| for (int i = 0; i < aliases.length; i++) { |
| newHosts = new MappedHost[hosts.length + 1]; |
| newHost = new MappedHost(); |
| newHost.name = aliases[i]; |
| newHost.contextList = contextList; |
| newHost.object = host; |
| if (insertMap(hosts, newHosts, newHost)) { |
| hosts = newHosts; |
| } |
| } |
| } |
| |
| |
| /** |
| * Remove a host from the mapper. |
| * |
| * @param name Virtual host name |
| */ |
| public synchronized void removeHost(String name) { |
| // Find and remove the old host |
| int pos = find(hosts, name); |
| if (pos < 0) { |
| return; |
| } |
| Host host = hosts[pos].object; |
| MappedHost[] newHosts = new MappedHost[hosts.length - 1]; |
| if (removeMap(hosts, newHosts, name)) { |
| hosts = newHosts; |
| } |
| // Remove all aliases (they will map to the same host object) |
| for (int i = 0; i < newHosts.length; i++) { |
| if (newHosts[i].object == host) { |
| MappedHost[] newHosts2 = new MappedHost[hosts.length - 1]; |
| if (removeMap(hosts, newHosts2, newHosts[i].name)) { |
| hosts = newHosts2; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add an alias to an existing host. |
| * @param name The name of the host |
| * @param alias The alias to add |
| */ |
| public synchronized void addHostAlias(String name, String alias) { |
| int pos = find(hosts, name); |
| if (pos < 0) { |
| // Should not be adding an alias for a host that doesn't exist but |
| // just in case... |
| return; |
| } |
| MappedHost realHost = hosts[pos]; |
| |
| MappedHost[] newHosts = new MappedHost[hosts.length + 1]; |
| MappedHost newHost = new MappedHost(); |
| newHost.name = alias; |
| newHost.contextList = realHost.contextList; |
| newHost.object = realHost.object; |
| if (insertMap(hosts, newHosts, newHost)) { |
| hosts = newHosts; |
| } |
| } |
| |
| /** |
| * Remove a host alias |
| * @param alias The alias to remove |
| */ |
| public synchronized void removeHostAlias(String alias) { |
| // Find and remove the alias |
| int pos = find(hosts, alias); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost[] newHosts = new MappedHost[hosts.length - 1]; |
| if (removeMap(hosts, newHosts, alias)) { |
| hosts = newHosts; |
| } |
| |
| } |
| |
| |
| /** |
| * Add a new Context to an existing Host. |
| * |
| * @param hostName Virtual host name this context belongs to |
| * @param host Host object |
| * @param path Context path |
| * @param version Context version |
| * @param context Context object |
| * @param welcomeResources Welcome files defined for this context |
| * @param resources Static resources of the context |
| */ |
| public void addContextVersion(String hostName, Host host, String path, |
| String version, Context context, String[] welcomeResources, |
| WebResourceRoot resources) { |
| |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if( pos <0 ) { |
| addHost(hostName, new String[0], host); |
| hosts = this.hosts; |
| pos = find(hosts, hostName); |
| } |
| if (pos < 0) { |
| log.error("No host found: " + hostName); |
| } |
| MappedHost mappedHost = hosts[pos]; |
| if (mappedHost.name.equals(hostName)) { |
| int slashCount = slashCount(path); |
| synchronized (mappedHost) { |
| MappedContext[] contexts = mappedHost.contextList.contexts; |
| // Update nesting |
| if (slashCount > mappedHost.contextList.nesting) { |
| mappedHost.contextList.nesting = slashCount; |
| } |
| int pos2 = find(contexts, path); |
| if (pos2 < 0 || !path.equals(contexts[pos2].name)) { |
| MappedContext newContext = new MappedContext(); |
| newContext.name = path; |
| MappedContext[] newContexts = new MappedContext[contexts.length + 1]; |
| if (insertMap(contexts, newContexts, newContext)) { |
| mappedHost.contextList.contexts = newContexts; |
| } |
| pos2 = find(newContexts, path); |
| } |
| |
| MappedContext mappedContext = mappedHost.contextList.contexts[pos2]; |
| |
| ContextVersion[] contextVersions = mappedContext.versions; |
| ContextVersion[] newContextVersions = |
| new ContextVersion[contextVersions.length + 1]; |
| ContextVersion newContextVersion = new ContextVersion(); |
| newContextVersion.path = path; |
| newContextVersion.name = version; |
| newContextVersion.object = context; |
| newContextVersion.welcomeResources = welcomeResources; |
| newContextVersion.resources = resources; |
| if (insertMap(contextVersions, newContextVersions, newContextVersion)) { |
| mappedContext.versions = newContextVersions; |
| contextObjectToContextVersionMap.put( |
| context, newContextVersion); |
| } |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Remove a context from an existing host. |
| * |
| * @param ctxt The actual context |
| * @param hostName Virtual host name this context belongs to |
| * @param path Context path |
| * @param version Context version |
| */ |
| public void removeContextVersion(Context ctxt, String hostName, |
| String path, String version) { |
| |
| contextObjectToContextVersionMap.remove(ctxt); |
| |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| synchronized (host) { |
| MappedContext[] contexts = host.contextList.contexts; |
| if (contexts.length == 0 ){ |
| return; |
| } |
| |
| int pos2 = find(contexts, path); |
| if (pos2 < 0 || !path.equals(contexts[pos2].name)) { |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| |
| ContextVersion[] contextVersions = context.versions; |
| ContextVersion[] newContextVersions = |
| new ContextVersion[contextVersions.length - 1]; |
| if (removeMap(contextVersions, newContextVersions, version)) { |
| context.versions = newContextVersions; |
| |
| if (context.versions.length == 0) { |
| // Remove the context |
| MappedContext[] newContexts = new MappedContext[contexts.length -1]; |
| if (removeMap(contexts, newContexts, path)) { |
| host.contextList.contexts = newContexts; |
| // Recalculate nesting |
| host.contextList.nesting = 0; |
| for (int i = 0; i < newContexts.length; i++) { |
| int slashCount = slashCount(newContexts[i].name); |
| if (slashCount > host.contextList.nesting) { |
| host.contextList.nesting = slashCount; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| public void addWrapper(String hostName, String contextPath, String version, |
| String path, Wrapper wrapper, boolean jspWildCard, |
| boolean resourceOnly) { |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| MappedContext[] contexts = host.contextList.contexts; |
| int pos2 = find(contexts, contextPath); |
| if (pos2 < 0) { |
| log.error("No context found: " + contextPath ); |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| if (context.name.equals(contextPath)) { |
| ContextVersion[] contextVersions = context.versions; |
| int pos3 = find(contextVersions, version); |
| if( pos3<0 ) { |
| log.error("No context version found: " + contextPath + " " + |
| version); |
| return; |
| } |
| ContextVersion contextVersion = contextVersions[pos3]; |
| if (contextVersion.name.equals(version)) { |
| addWrapper(contextVersion, path, wrapper, jspWildCard, |
| resourceOnly); |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Adds a wrapper to the given context. |
| * |
| * @param context The context to which to add the wrapper |
| * @param path Wrapper mapping |
| * @param wrapper The Wrapper object |
| * @param jspWildCard true if the wrapper corresponds to the JspServlet |
| * @param resourceOnly true if this wrapper always expects a physical |
| * resource to be present (such as a JSP) |
| * and the mapping path contains a wildcard; false otherwise |
| */ |
| protected void addWrapper(ContextVersion context, String path, |
| Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) { |
| |
| synchronized (context) { |
| MappedWrapper newWrapper = new MappedWrapper(); |
| newWrapper.object = wrapper; |
| newWrapper.jspWildCard = jspWildCard; |
| newWrapper.resourceOnly = resourceOnly; |
| if (path.endsWith("/*")) { |
| // Wildcard wrapper |
| newWrapper.name = path.substring(0, path.length() - 2); |
| MappedWrapper[] oldWrappers = context.wildcardWrappers; |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length + 1]; |
| if (insertMap(oldWrappers, newWrappers, newWrapper)) { |
| context.wildcardWrappers = newWrappers; |
| int slashCount = slashCount(newWrapper.name); |
| if (slashCount > context.nesting) { |
| context.nesting = slashCount; |
| } |
| } |
| } else if (path.startsWith("*.")) { |
| // Extension wrapper |
| newWrapper.name = path.substring(2); |
| MappedWrapper[] oldWrappers = context.extensionWrappers; |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length + 1]; |
| if (insertMap(oldWrappers, newWrappers, newWrapper)) { |
| context.extensionWrappers = newWrappers; |
| } |
| } else if (path.equals("/")) { |
| // Default wrapper |
| newWrapper.name = ""; |
| context.defaultWrapper = newWrapper; |
| } else { |
| // Exact wrapper |
| if (path.length() == 0) { |
| // Special case for the Context Root mapping which is |
| // treated as an exact match |
| newWrapper.name = "/"; |
| } else { |
| newWrapper.name = path; |
| } |
| MappedWrapper[] oldWrappers = context.exactWrappers; |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length + 1]; |
| if (insertMap(oldWrappers, newWrappers, newWrapper)) { |
| context.exactWrappers = newWrappers; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Remove a wrapper from an existing context. |
| * |
| * @param hostName Virtual host name this wrapper belongs to |
| * @param contextPath Context path this wrapper belongs to |
| * @param path Wrapper mapping |
| */ |
| public void removeWrapper |
| (String hostName, String contextPath, String version, String path) { |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| MappedContext[] contexts = host.contextList.contexts; |
| int pos2 = find(contexts, contextPath); |
| if (pos2 < 0) { |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| if (context.name.equals(contextPath)) { |
| ContextVersion[] contextVersions = context.versions; |
| int pos3 = find(contextVersions, version); |
| if( pos3<0 ) { |
| return; |
| } |
| ContextVersion contextVersion = contextVersions[pos3]; |
| if (contextVersion.name.equals(version)) { |
| removeWrapper(contextVersion, path); |
| } |
| } |
| } |
| } |
| |
| protected void removeWrapper(ContextVersion context, String path) { |
| |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("mapper.removeWrapper", context.name, path)); |
| } |
| |
| synchronized (context) { |
| if (path.endsWith("/*")) { |
| // Wildcard wrapper |
| String name = path.substring(0, path.length() - 2); |
| MappedWrapper[] oldWrappers = context.wildcardWrappers; |
| if (oldWrappers.length == 0) { |
| return; |
| } |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length - 1]; |
| if (removeMap(oldWrappers, newWrappers, name)) { |
| // Recalculate nesting |
| context.nesting = 0; |
| for (int i = 0; i < newWrappers.length; i++) { |
| int slashCount = slashCount(newWrappers[i].name); |
| if (slashCount > context.nesting) { |
| context.nesting = slashCount; |
| } |
| } |
| context.wildcardWrappers = newWrappers; |
| } |
| } else if (path.startsWith("*.")) { |
| // Extension wrapper |
| String name = path.substring(2); |
| MappedWrapper[] oldWrappers = context.extensionWrappers; |
| if (oldWrappers.length == 0) { |
| return; |
| } |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length - 1]; |
| if (removeMap(oldWrappers, newWrappers, name)) { |
| context.extensionWrappers = newWrappers; |
| } |
| } else if (path.equals("/")) { |
| // Default wrapper |
| context.defaultWrapper = null; |
| } else { |
| // Exact wrapper |
| String name; |
| if (path.length() == 0) { |
| // Special case for the Context Root mapping which is |
| // treated as an exact match |
| name = "/"; |
| } else { |
| name = path; |
| } |
| MappedWrapper[] oldWrappers = context.exactWrappers; |
| if (oldWrappers.length == 0) { |
| return; |
| } |
| MappedWrapper[] newWrappers = |
| new MappedWrapper[oldWrappers.length - 1]; |
| if (removeMap(oldWrappers, newWrappers, name)) { |
| context.exactWrappers = newWrappers; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Add a welcome file to the given context. |
| * |
| * @param hostName |
| * @param contextPath |
| * @param welcomeFile |
| */ |
| public void addWelcomeFile(String hostName, String contextPath, |
| String version, String welcomeFile) { |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| MappedContext[] contexts = host.contextList.contexts; |
| int pos2 = find(contexts, contextPath); |
| if (pos2 < 0) { |
| log.error("No context found: " + contextPath ); |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| if (context.name.equals(contextPath)) { |
| ContextVersion[] contextVersions = context.versions; |
| int pos3 = find(contextVersions, version); |
| if( pos3<0 ) { |
| log.error("No context version found: " + contextPath + " " + |
| version); |
| return; |
| } |
| ContextVersion contextVersion = contextVersions[pos3]; |
| if (contextVersion.name.equals(version)) { |
| int len = contextVersion.welcomeResources.length + 1; |
| String[] newWelcomeResources = new String[len]; |
| System.arraycopy(contextVersion.welcomeResources, 0, |
| newWelcomeResources, 0, len - 1); |
| newWelcomeResources[len - 1] = welcomeFile; |
| contextVersion.welcomeResources = newWelcomeResources; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Remove a welcome file from the given context. |
| * |
| * @param hostName |
| * @param contextPath |
| * @param welcomeFile |
| */ |
| public void removeWelcomeFile(String hostName, String contextPath, |
| String version, String welcomeFile) { |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| MappedContext[] contexts = host.contextList.contexts; |
| int pos2 = find(contexts, contextPath); |
| if (pos2 < 0) { |
| log.error("No context found: " + contextPath ); |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| if (context.name.equals(contextPath)) { |
| ContextVersion[] contextVersions = context.versions; |
| int pos3 = find(contextVersions, version); |
| if( pos3<0 ) { |
| log.error("No context version found: " + contextPath + " " + |
| version); |
| return; |
| } |
| ContextVersion contextVersion = contextVersions[pos3]; |
| if (contextVersion.name.equals(version)) { |
| int match = -1; |
| for (int i = 0; i < contextVersion.welcomeResources.length; i++) { |
| if (welcomeFile.equals(contextVersion.welcomeResources[i])) { |
| match = i; |
| break; |
| } |
| } |
| if (match > -1) { |
| int len = contextVersion.welcomeResources.length - 1; |
| String[] newWelcomeResources = new String[len]; |
| System.arraycopy(contextVersion.welcomeResources, 0, |
| newWelcomeResources, 0, match); |
| if (match < len) { |
| System.arraycopy(contextVersion.welcomeResources, match + 1, |
| newWelcomeResources, match, len - match); |
| } |
| contextVersion.welcomeResources = newWelcomeResources; |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Clear the welcome files for the given context. |
| * |
| * @param hostName |
| * @param contextPath |
| */ |
| public void clearWelcomeFiles(String hostName, String contextPath, |
| String version) { |
| MappedHost[] hosts = this.hosts; |
| int pos = find(hosts, hostName); |
| if (pos < 0) { |
| return; |
| } |
| MappedHost host = hosts[pos]; |
| if (host.name.equals(hostName)) { |
| MappedContext[] contexts = host.contextList.contexts; |
| int pos2 = find(contexts, contextPath); |
| if (pos2 < 0) { |
| log.error("No context found: " + contextPath ); |
| return; |
| } |
| MappedContext context = contexts[pos2]; |
| if (context.name.equals(contextPath)) { |
| ContextVersion[] contextVersions = context.versions; |
| int pos3 = find(contextVersions, version); |
| if( pos3<0 ) { |
| log.error("No context version found: " + contextPath + " " + |
| version); |
| return; |
| } |
| ContextVersion contextVersion = contextVersions[pos3]; |
| if (contextVersion.name.equals(version)) { |
| contextVersion.welcomeResources = new String[0]; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * Map the specified host name and URI, mutating the given mapping data. |
| * |
| * @param host Virtual host name |
| * @param uri URI |
| * @param mappingData This structure will contain the result of the mapping |
| * operation |
| */ |
| public void map(MessageBytes host, MessageBytes uri, String version, |
| MappingData mappingData) |
| throws Exception { |
| |
| if (host.isNull()) { |
| host.getCharChunk().append(defaultHostName); |
| } |
| host.toChars(); |
| uri.toChars(); |
| internalMap(host.getCharChunk(), uri.getCharChunk(), version, |
| mappingData); |
| |
| } |
| |
| |
| /** |
| * Map the specified URI relative to the context, |
| * mutating the given mapping data. |
| * |
| * @param context The actual context |
| * @param uri URI |
| * @param mappingData This structure will contain the result of the mapping |
| * operation |
| */ |
| public void map(Context context, MessageBytes uri, |
| MappingData mappingData) throws Exception { |
| |
| ContextVersion contextVersion = |
| contextObjectToContextVersionMap.get(context); |
| uri.toChars(); |
| CharChunk uricc = uri.getCharChunk(); |
| uricc.setLimit(-1); |
| internalMapWrapper(contextVersion, uricc, mappingData); |
| |
| } |
| |
| |
| // -------------------------------------------------------- Private Methods |
| |
| |
| /** |
| * Map the specified URI. |
| */ |
| private final void internalMap(CharChunk host, CharChunk uri, |
| String version, MappingData mappingData) throws Exception { |
| |
| uri.setLimit(-1); |
| |
| MappedContext[] contexts = null; |
| MappedContext context = null; |
| ContextVersion contextVersion = null; |
| |
| int nesting = 0; |
| |
| // Virtual host mapping |
| if (mappingData.host == null) { |
| MappedHost[] hosts = this.hosts; |
| int pos = findIgnoreCase(hosts, host); |
| if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) { |
| mappingData.host = hosts[pos].object; |
| contexts = hosts[pos].contextList.contexts; |
| nesting = hosts[pos].contextList.nesting; |
| } else { |
| if (defaultHostName == null) { |
| return; |
| } |
| pos = find(hosts, defaultHostName); |
| if ((pos != -1) && (defaultHostName.equals(hosts[pos].name))) { |
| mappingData.host = hosts[pos].object; |
| contexts = hosts[pos].contextList.contexts; |
| nesting = hosts[pos].contextList.nesting; |
| } else { |
| return; |
| } |
| } |
| } |
| |
| // Context mapping |
| if (mappingData.context == null && contexts != null) { |
| int pos = find(contexts, uri); |
| if (pos == -1) { |
| return; |
| } |
| |
| int lastSlash = -1; |
| int uriEnd = uri.getEnd(); |
| int length = -1; |
| boolean found = false; |
| while (pos >= 0) { |
| if (uri.startsWith(contexts[pos].name)) { |
| length = contexts[pos].name.length(); |
| if (uri.getLength() == length) { |
| found = true; |
| break; |
| } else if (uri.startsWithIgnoreCase("/", length)) { |
| found = true; |
| break; |
| } |
| } |
| if (lastSlash == -1) { |
| lastSlash = nthSlash(uri, nesting + 1); |
| } else { |
| lastSlash = lastSlash(uri); |
| } |
| uri.setEnd(lastSlash); |
| pos = find(contexts, uri); |
| } |
| uri.setEnd(uriEnd); |
| |
| if (!found) { |
| if (contexts[0].name.equals("")) { |
| context = contexts[0]; |
| } |
| } else { |
| context = contexts[pos]; |
| } |
| if (context != null) { |
| mappingData.contextPath.setString(context.name); |
| } |
| } |
| |
| if (context != null) { |
| ContextVersion[] contextVersions = context.versions; |
| int versionCount = contextVersions.length; |
| if (versionCount > 1) { |
| Context[] contextObjects = new Context[contextVersions.length]; |
| for (int i = 0; i < contextObjects.length; i++) { |
| contextObjects[i] = contextVersions[i].object; |
| } |
| mappingData.contexts = contextObjects; |
| } |
| |
| if (version == null) { |
| // Return the latest version |
| contextVersion = contextVersions[versionCount - 1]; |
| } else { |
| int pos = find(contextVersions, version); |
| if (pos < 0 || !contextVersions[pos].name.equals(version)) { |
| // Return the latest version |
| contextVersion = contextVersions[versionCount - 1]; |
| } else { |
| contextVersion = contextVersions[pos]; |
| } |
| } |
| mappingData.context = contextVersion.object; |
| } |
| |
| // Wrapper mapping |
| if ((contextVersion != null) && (mappingData.wrapper == null)) { |
| internalMapWrapper(contextVersion, uri, mappingData); |
| } |
| |
| } |
| |
| |
| /** |
| * Wrapper mapping. |
| */ |
| private final void internalMapWrapper(ContextVersion contextVersion, |
| CharChunk path, |
| MappingData mappingData) |
| throws Exception { |
| |
| int pathOffset = path.getOffset(); |
| int pathEnd = path.getEnd(); |
| int servletPath = pathOffset; |
| boolean noServletPath = false; |
| |
| int length = contextVersion.path.length(); |
| if (length != (pathEnd - pathOffset)) { |
| servletPath = pathOffset + length; |
| } else { |
| noServletPath = true; |
| path.append('/'); |
| pathOffset = path.getOffset(); |
| pathEnd = path.getEnd(); |
| servletPath = pathOffset+length; |
| } |
| |
| path.setOffset(servletPath); |
| |
| // Rule 1 -- Exact Match |
| MappedWrapper[] exactWrappers = contextVersion.exactWrappers; |
| internalMapExactWrapper(exactWrappers, path, mappingData); |
| |
| // Rule 2 -- Prefix Match |
| boolean checkJspWelcomeFiles = false; |
| MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers; |
| if (mappingData.wrapper == null) { |
| internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, |
| path, mappingData); |
| if (mappingData.wrapper != null && mappingData.jspWildCard) { |
| char[] buf = path.getBuffer(); |
| if (buf[pathEnd - 1] == '/') { |
| /* |
| * Path ending in '/' was mapped to JSP servlet based on |
| * wildcard match (e.g., as specified in url-pattern of a |
| * jsp-property-group. |
| * Force the context's welcome files, which are interpreted |
| * as JSP files (since they match the url-pattern), to be |
| * considered. See Bugzilla 27664. |
| */ |
| mappingData.wrapper = null; |
| checkJspWelcomeFiles = true; |
| } else { |
| // See Bugzilla 27704 |
| mappingData.wrapperPath.setChars(buf, path.getStart(), |
| path.getLength()); |
| mappingData.pathInfo.recycle(); |
| } |
| } |
| } |
| |
| if(mappingData.wrapper == null && noServletPath) { |
| // The path is empty, redirect to "/" |
| mappingData.redirectPath.setChars |
| (path.getBuffer(), pathOffset, pathEnd-pathOffset); |
| path.setEnd(pathEnd - 1); |
| return; |
| } |
| |
| // Rule 3 -- Extension Match |
| MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers; |
| if (mappingData.wrapper == null && !checkJspWelcomeFiles) { |
| internalMapExtensionWrapper(extensionWrappers, path, mappingData, |
| true); |
| } |
| |
| // Rule 4 -- Welcome resources processing for servlets |
| if (mappingData.wrapper == null) { |
| boolean checkWelcomeFiles = checkJspWelcomeFiles; |
| if (!checkWelcomeFiles) { |
| char[] buf = path.getBuffer(); |
| checkWelcomeFiles = (buf[pathEnd - 1] == '/'); |
| } |
| if (checkWelcomeFiles) { |
| for (int i = 0; (i < contextVersion.welcomeResources.length) |
| && (mappingData.wrapper == null); i++) { |
| path.setOffset(pathOffset); |
| path.setEnd(pathEnd); |
| path.append(contextVersion.welcomeResources[i], 0, |
| contextVersion.welcomeResources[i].length()); |
| path.setOffset(servletPath); |
| |
| // Rule 4a -- Welcome resources processing for exact macth |
| internalMapExactWrapper(exactWrappers, path, mappingData); |
| |
| // Rule 4b -- Welcome resources processing for prefix match |
| if (mappingData.wrapper == null) { |
| internalMapWildcardWrapper |
| (wildcardWrappers, contextVersion.nesting, |
| path, mappingData); |
| } |
| |
| // Rule 4c -- Welcome resources processing |
| // for physical folder |
| if (mappingData.wrapper == null |
| && contextVersion.resources != null) { |
| String pathStr = path.toString(); |
| WebResource file = |
| contextVersion.resources.getResource(pathStr); |
| if (file != null && file.isFile()) { |
| internalMapExtensionWrapper(extensionWrappers, path, |
| mappingData, true); |
| if (mappingData.wrapper == null |
| && contextVersion.defaultWrapper != null) { |
| mappingData.wrapper = |
| contextVersion.defaultWrapper.object; |
| mappingData.requestPath.setChars |
| (path.getBuffer(), path.getStart(), |
| path.getLength()); |
| mappingData.wrapperPath.setChars |
| (path.getBuffer(), path.getStart(), |
| path.getLength()); |
| mappingData.requestPath.setString(pathStr); |
| mappingData.wrapperPath.setString(pathStr); |
| } |
| } |
| } |
| } |
| |
| path.setOffset(servletPath); |
| path.setEnd(pathEnd); |
| } |
| |
| } |
| |
| /* welcome file processing - take 2 |
| * Now that we have looked for welcome files with a physical |
| * backing, now look for an extension mapping listed |
| * but may not have a physical backing to it. This is for |
| * the case of index.jsf, index.do, etc. |
| * A watered down version of rule 4 |
| */ |
| if (mappingData.wrapper == null) { |
| boolean checkWelcomeFiles = checkJspWelcomeFiles; |
| if (!checkWelcomeFiles) { |
| char[] buf = path.getBuffer(); |
| checkWelcomeFiles = (buf[pathEnd - 1] == '/'); |
| } |
| if (checkWelcomeFiles) { |
| for (int i = 0; (i < contextVersion.welcomeResources.length) |
| && (mappingData.wrapper == null); i++) { |
| path.setOffset(pathOffset); |
| path.setEnd(pathEnd); |
| path.append(contextVersion.welcomeResources[i], 0, |
| contextVersion.welcomeResources[i].length()); |
| path.setOffset(servletPath); |
| internalMapExtensionWrapper(extensionWrappers, path, |
| mappingData, false); |
| } |
| |
| path.setOffset(servletPath); |
| path.setEnd(pathEnd); |
| } |
| } |
| |
| |
| // Rule 7 -- Default servlet |
| if (mappingData.wrapper == null && !checkJspWelcomeFiles) { |
| if (contextVersion.defaultWrapper != null) { |
| mappingData.wrapper = contextVersion.defaultWrapper.object; |
| mappingData.requestPath.setChars |
| (path.getBuffer(), path.getStart(), path.getLength()); |
| mappingData.wrapperPath.setChars |
| (path.getBuffer(), path.getStart(), path.getLength()); |
| } |
| // Redirection to a folder |
| char[] buf = path.getBuffer(); |
| if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') { |
| String pathStr = path.toString(); |
| WebResource file = |
| contextVersion.resources.getResource(pathStr); |
| if (file != null && file.isDirectory()) { |
| // Note: this mutates the path: do not do any processing |
| // after this (since we set the redirectPath, there |
| // shouldn't be any) |
| path.setOffset(pathOffset); |
| path.append('/'); |
| mappingData.redirectPath.setChars |
| (path.getBuffer(), path.getStart(), path.getLength()); |
| } else { |
| mappingData.requestPath.setString(pathStr); |
| mappingData.wrapperPath.setString(pathStr); |
| } |
| } |
| } |
| |
| path.setOffset(pathOffset); |
| path.setEnd(pathEnd); |
| |
| } |
| |
| |
| /** |
| * Exact mapping. |
| */ |
| private final void internalMapExactWrapper |
| (MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) { |
| int pos = find(wrappers, path); |
| if ((pos != -1) && (path.equals(wrappers[pos].name))) { |
| mappingData.requestPath.setString(wrappers[pos].name); |
| mappingData.wrapper = wrappers[pos].object; |
| if (path.equals("/")) { |
| // Special handling for Context Root mapped servlet |
| mappingData.pathInfo.setString("/"); |
| mappingData.wrapperPath.setString(""); |
| // This seems wrong but it is what the spec says... |
| mappingData.contextPath.setString(""); |
| } else { |
| mappingData.wrapperPath.setString(wrappers[pos].name); |
| } |
| } |
| } |
| |
| |
| /** |
| * Wildcard mapping. |
| */ |
| private final void internalMapWildcardWrapper |
| (MappedWrapper[] wrappers, int nesting, CharChunk path, |
| MappingData mappingData) { |
| |
| int pathEnd = path.getEnd(); |
| |
| int lastSlash = -1; |
| int length = -1; |
| int pos = find(wrappers, path); |
| if (pos != -1) { |
| boolean found = false; |
| while (pos >= 0) { |
| if (path.startsWith(wrappers[pos].name)) { |
| length = wrappers[pos].name.length(); |
| if (path.getLength() == length) { |
| found = true; |
| break; |
| } else if (path.startsWithIgnoreCase("/", length)) { |
| found = true; |
| break; |
| } |
| } |
| if (lastSlash == -1) { |
| lastSlash = nthSlash(path, nesting + 1); |
| } else { |
| lastSlash = lastSlash(path); |
| } |
| path.setEnd(lastSlash); |
| pos = find(wrappers, path); |
| } |
| path.setEnd(pathEnd); |
| if (found) { |
| mappingData.wrapperPath.setString(wrappers[pos].name); |
| if (path.getLength() > length) { |
| mappingData.pathInfo.setChars |
| (path.getBuffer(), |
| path.getOffset() + length, |
| path.getLength() - length); |
| } |
| mappingData.requestPath.setChars |
| (path.getBuffer(), path.getOffset(), path.getLength()); |
| mappingData.wrapper = wrappers[pos].object; |
| mappingData.jspWildCard = wrappers[pos].jspWildCard; |
| } |
| } |
| } |
| |
| |
| /** |
| * Extension mappings. |
| * |
| * @param wrappers Set of wrappers to check for matches |
| * @param path Path to map |
| * @param mappingData Mapping data for result |
| * @param resourceExpected Is this mapping expecting to find a resource |
| */ |
| private final void internalMapExtensionWrapper(MappedWrapper[] wrappers, |
| CharChunk path, MappingData mappingData, boolean resourceExpected) { |
| char[] buf = path.getBuffer(); |
| int pathEnd = path.getEnd(); |
| int servletPath = path.getOffset(); |
| int slash = -1; |
| for (int i = pathEnd - 1; i >= servletPath; i--) { |
| if (buf[i] == '/') { |
| slash = i; |
| break; |
| } |
| } |
| if (slash >= 0) { |
| int period = -1; |
| for (int i = pathEnd - 1; i > slash; i--) { |
| if (buf[i] == '.') { |
| period = i; |
| break; |
| } |
| } |
| if (period >= 0) { |
| path.setOffset(period + 1); |
| path.setEnd(pathEnd); |
| int pos = find(wrappers, path); |
| if ((pos != -1) && (path.equals(wrappers[pos].name)) && |
| (resourceExpected || !wrappers[pos].resourceOnly)) { |
| mappingData.wrapperPath.setChars |
| (buf, servletPath, pathEnd - servletPath); |
| mappingData.requestPath.setChars |
| (buf, servletPath, pathEnd - servletPath); |
| mappingData.wrapper = wrappers[pos].object; |
| } |
| path.setOffset(servletPath); |
| path.setEnd(pathEnd); |
| } |
| } |
| } |
| |
| |
| /** |
| * Find a map element given its name in a sorted array of map elements. |
| * This will return the index for the closest inferior or equal item in the |
| * given array. |
| */ |
| private static final <T> int find(MapElement<T>[] map, CharChunk name) { |
| return find(map, name, name.getStart(), name.getEnd()); |
| } |
| |
| |
| /** |
| * Find a map element given its name in a sorted array of map elements. |
| * This will return the index for the closest inferior or equal item in the |
| * given array. |
| */ |
| private static final <T> int find(MapElement<T>[] map, CharChunk name, |
| int start, int end) { |
| |
| int a = 0; |
| int b = map.length - 1; |
| |
| // Special cases: -1 and 0 |
| if (b == -1) { |
| return -1; |
| } |
| |
| if (compare(name, start, end, map[0].name) < 0 ) { |
| return -1; |
| } |
| if (b == 0) { |
| return 0; |
| } |
| |
| int i = 0; |
| while (true) { |
| i = (b + a) / 2; |
| int result = compare(name, start, end, map[i].name); |
| if (result == 1) { |
| a = i; |
| } else if (result == 0) { |
| return i; |
| } else { |
| b = i; |
| } |
| if ((b - a) == 1) { |
| int result2 = compare(name, start, end, map[b].name); |
| if (result2 < 0) { |
| return a; |
| } else { |
| return b; |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Find a map element given its name in a sorted array of map elements. |
| * This will return the index for the closest inferior or equal item in the |
| * given array. |
| */ |
| private static final <T> int findIgnoreCase(MapElement<T>[] map, CharChunk name) { |
| return findIgnoreCase(map, name, name.getStart(), name.getEnd()); |
| } |
| |
| |
| /** |
| * Find a map element given its name in a sorted array of map elements. |
| * This will return the index for the closest inferior or equal item in the |
| * given array. |
| */ |
| private static final <T> int findIgnoreCase(MapElement<T>[] map, CharChunk name, |
| int start, int end) { |
| |
| int a = 0; |
| int b = map.length - 1; |
| |
| // Special cases: -1 and 0 |
| if (b == -1) { |
| return -1; |
| } |
| if (compareIgnoreCase(name, start, end, map[0].name) < 0 ) { |
| return -1; |
| } |
| if (b == 0) { |
| return 0; |
| } |
| |
| int i = 0; |
| while (true) { |
| i = (b + a) / 2; |
| int result = compareIgnoreCase(name, start, end, map[i].name); |
| if (result == 1) { |
| a = i; |
| } else if (result == 0) { |
| return i; |
| } else { |
| b = i; |
| } |
| if ((b - a) == 1) { |
| int result2 = compareIgnoreCase(name, start, end, map[b].name); |
| if (result2 < 0) { |
| return a; |
| } else { |
| return b; |
| } |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Find a map element given its name in a sorted array of map elements. |
| * This will return the index for the closest inferior or equal item in the |
| * given array. |
| */ |
| private static final <T> int find(MapElement<T>[] map, String name) { |
| |
| int a = 0; |
| int b = map.length - 1; |
| |
| // Special cases: -1 and 0 |
| if (b == -1) { |
| return -1; |
| } |
| |
| if (name.compareTo(map[0].name) < 0) { |
| return -1; |
| } |
| if (b == 0) { |
| return 0; |
| } |
| |
| int i = 0; |
| while (true) { |
| i = (b + a) / 2; |
| int result = name.compareTo(map[i].name); |
| if (result > 0) { |
| a = i; |
| } else if (result == 0) { |
| return i; |
| } else { |
| b = i; |
| } |
| if ((b - a) == 1) { |
| int result2 = name.compareTo(map[b].name); |
| if (result2 < 0) { |
| return a; |
| } else { |
| return b; |
| } |
| } |
| } |
| |
| } |
| |
| |
| /** |
| * Compare given char chunk with String. |
| * Return -1, 0 or +1 if inferior, equal, or superior to the String. |
| */ |
| private static final int compare(CharChunk name, int start, int end, |
| String compareTo) { |
| int result = 0; |
| char[] c = name.getBuffer(); |
| int len = compareTo.length(); |
| if ((end - start) < len) { |
| len = end - start; |
| } |
| for (int i = 0; (i < len) && (result == 0); i++) { |
| if (c[i + start] > compareTo.charAt(i)) { |
| result = 1; |
| } else if (c[i + start] < compareTo.charAt(i)) { |
| result = -1; |
| } |
| } |
| if (result == 0) { |
| if (compareTo.length() > (end - start)) { |
| result = -1; |
| } else if (compareTo.length() < (end - start)) { |
| result = 1; |
| } |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Compare given char chunk with String ignoring case. |
| * Return -1, 0 or +1 if inferior, equal, or superior to the String. |
| */ |
| private static final int compareIgnoreCase(CharChunk name, int start, int end, |
| String compareTo) { |
| int result = 0; |
| char[] c = name.getBuffer(); |
| int len = compareTo.length(); |
| if ((end - start) < len) { |
| len = end - start; |
| } |
| for (int i = 0; (i < len) && (result == 0); i++) { |
| if (Ascii.toLower(c[i + start]) > Ascii.toLower(compareTo.charAt(i))) { |
| result = 1; |
| } else if (Ascii.toLower(c[i + start]) < Ascii.toLower(compareTo.charAt(i))) { |
| result = -1; |
| } |
| } |
| if (result == 0) { |
| if (compareTo.length() > (end - start)) { |
| result = -1; |
| } else if (compareTo.length() < (end - start)) { |
| result = 1; |
| } |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Find the position of the last slash in the given char chunk. |
| */ |
| private static final int lastSlash(CharChunk name) { |
| |
| char[] c = name.getBuffer(); |
| int end = name.getEnd(); |
| int start = name.getStart(); |
| int pos = end; |
| |
| while (pos > start) { |
| if (c[--pos] == '/') { |
| break; |
| } |
| } |
| |
| return (pos); |
| |
| } |
| |
| |
| /** |
| * Find the position of the nth slash, in the given char chunk. |
| */ |
| private static final int nthSlash(CharChunk name, int n) { |
| |
| char[] c = name.getBuffer(); |
| int end = name.getEnd(); |
| int start = name.getStart(); |
| int pos = start; |
| int count = 0; |
| |
| while (pos < end) { |
| if ((c[pos++] == '/') && ((++count) == n)) { |
| pos--; |
| break; |
| } |
| } |
| |
| return (pos); |
| |
| } |
| |
| |
| /** |
| * Return the slash count in a given string. |
| */ |
| private static final int slashCount(String name) { |
| int pos = -1; |
| int count = 0; |
| while ((pos = name.indexOf('/', pos + 1)) != -1) { |
| count++; |
| } |
| return count; |
| } |
| |
| |
| /** |
| * Insert into the right place in a sorted MapElement array, and prevent |
| * duplicates. |
| */ |
| private static final <T> boolean insertMap |
| (MapElement<T>[] oldMap, MapElement<T>[] newMap, MapElement<T> newElement) { |
| int pos = find(oldMap, newElement.name); |
| if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) { |
| return false; |
| } |
| System.arraycopy(oldMap, 0, newMap, 0, pos + 1); |
| newMap[pos + 1] = newElement; |
| System.arraycopy |
| (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1); |
| return true; |
| } |
| |
| |
| /** |
| * Insert into the right place in a sorted MapElement array. |
| */ |
| private static final <T> boolean removeMap |
| (MapElement<T>[] oldMap, MapElement<T>[] newMap, String name) { |
| int pos = find(oldMap, name); |
| if ((pos != -1) && (name.equals(oldMap[pos].name))) { |
| System.arraycopy(oldMap, 0, newMap, 0, pos); |
| System.arraycopy(oldMap, pos + 1, newMap, pos, |
| oldMap.length - pos - 1); |
| return true; |
| } |
| return false; |
| } |
| |
| |
| // ------------------------------------------------- MapElement Inner Class |
| |
| |
| protected abstract static class MapElement<T> { |
| |
| public String name = null; |
| public T object = null; |
| |
| } |
| |
| |
| // ------------------------------------------------------- Host Inner Class |
| |
| |
| protected static final class MappedHost |
| extends MapElement<Host> { |
| |
| public ContextList contextList = null; |
| |
| } |
| |
| |
| // ------------------------------------------------ ContextList Inner Class |
| |
| |
| protected static final class ContextList { |
| |
| public MappedContext[] contexts = new MappedContext[0]; |
| public int nesting = 0; |
| |
| } |
| |
| |
| // ---------------------------------------------------- Context Inner Class |
| |
| |
| protected static final class MappedContext extends MapElement<Context> { |
| public ContextVersion[] versions = new ContextVersion[0]; |
| } |
| |
| |
| protected static final class ContextVersion extends MapElement<Context> { |
| public String path = null; |
| public String[] welcomeResources = new String[0]; |
| public WebResourceRoot resources = null; |
| public MappedWrapper defaultWrapper = null; |
| public MappedWrapper[] exactWrappers = new MappedWrapper[0]; |
| public MappedWrapper[] wildcardWrappers = new MappedWrapper[0]; |
| public MappedWrapper[] extensionWrappers = new MappedWrapper[0]; |
| public int nesting = 0; |
| |
| } |
| |
| |
| // ---------------------------------------------------- Wrapper Inner Class |
| |
| |
| protected static class MappedWrapper |
| extends MapElement<Wrapper> { |
| |
| public boolean jspWildCard = false; |
| public boolean resourceOnly = false; |
| } |
| } |