1753 lines
62 KiB
Java
1753 lines
62 KiB
Java
/*
|
|
* 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.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
|
|
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.catalina.servlet4preview.http.MappingMatch;
|
|
import org.apache.juli.logging.Log;
|
|
import org.apache.juli.logging.LogFactory;
|
|
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 Log log = LogFactory.getLog(Mapper.class);
|
|
|
|
private static final StringManager sm = StringManager.getManager(Mapper.class);
|
|
|
|
// ----------------------------------------------------- Instance Variables
|
|
|
|
|
|
/**
|
|
* Array containing the virtual hosts definitions.
|
|
*/
|
|
// Package private to facilitate testing
|
|
volatile MappedHost[] hosts = new MappedHost[0];
|
|
|
|
|
|
/**
|
|
* Default host name.
|
|
*/
|
|
private String defaultHostName = null;
|
|
private volatile MappedHost defaultHost = null;
|
|
|
|
|
|
/**
|
|
* Mapping from Context object to Context version to support
|
|
* RequestDispatcher mappings.
|
|
*/
|
|
private final Map<Context, ContextVersion> contextObjectToContextVersionMap =
|
|
new ConcurrentHashMap<>();
|
|
|
|
|
|
// --------------------------------------------------------- Public Methods
|
|
|
|
/**
|
|
* Set default host.
|
|
*
|
|
* @param defaultHostName Default host name
|
|
*/
|
|
public synchronized void setDefaultHostName(String defaultHostName) {
|
|
this.defaultHostName = renameWildcardHost(defaultHostName);
|
|
if (this.defaultHostName == null) {
|
|
defaultHost = null;
|
|
} else {
|
|
defaultHost = exactFind(hosts, this.defaultHostName);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a new host to the mapper.
|
|
*
|
|
* @param name Virtual host name
|
|
* @param aliases Alias names for the virtual host
|
|
* @param host Host object
|
|
*/
|
|
public synchronized void addHost(String name, String[] aliases,
|
|
Host host) {
|
|
name = renameWildcardHost(name);
|
|
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
|
|
MappedHost newHost = new MappedHost(name, host);
|
|
if (insertMap(hosts, newHosts, newHost)) {
|
|
hosts = newHosts;
|
|
if (newHost.name.equals(defaultHostName)) {
|
|
defaultHost = newHost;
|
|
}
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(sm.getString("mapper.addHost.success", name));
|
|
}
|
|
} else {
|
|
MappedHost duplicate = hosts[find(hosts, name)];
|
|
if (duplicate.object == host) {
|
|
// The host is already registered in the mapper.
|
|
// E.g. it might have been added by addContextVersion()
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(sm.getString("mapper.addHost.sameHost", name));
|
|
}
|
|
newHost = duplicate;
|
|
} else {
|
|
log.error(sm.getString("mapper.duplicateHost", name,
|
|
duplicate.getRealHostName()));
|
|
// Do not add aliases, as removeHost(hostName) won't be able to
|
|
// remove them
|
|
return;
|
|
}
|
|
}
|
|
List<MappedHost> newAliases = new ArrayList<>(aliases.length);
|
|
for (String alias : aliases) {
|
|
alias = renameWildcardHost(alias);
|
|
MappedHost newAlias = new MappedHost(alias, newHost);
|
|
if (addHostAliasImpl(newAlias)) {
|
|
newAliases.add(newAlias);
|
|
}
|
|
}
|
|
newHost.addAliases(newAliases);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove a host from the mapper.
|
|
*
|
|
* @param name Virtual host name
|
|
*/
|
|
public synchronized void removeHost(String name) {
|
|
name = renameWildcardHost(name);
|
|
// Find and remove the old host
|
|
MappedHost host = exactFind(hosts, name);
|
|
if (host == null || host.isAlias()) {
|
|
return;
|
|
}
|
|
MappedHost[] newHosts = hosts.clone();
|
|
// Remove real host and all its aliases
|
|
int j = 0;
|
|
for (int i = 0; i < newHosts.length; i++) {
|
|
if (newHosts[i].getRealHost() != host) {
|
|
newHosts[j++] = newHosts[i];
|
|
}
|
|
}
|
|
hosts = Arrays.copyOf(newHosts, j);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
MappedHost realHost = exactFind(hosts, name);
|
|
if (realHost == null) {
|
|
// Should not be adding an alias for a host that doesn't exist but
|
|
// just in case...
|
|
return;
|
|
}
|
|
alias = renameWildcardHost(alias);
|
|
MappedHost newAlias = new MappedHost(alias, realHost);
|
|
if (addHostAliasImpl(newAlias)) {
|
|
realHost.addAlias(newAlias);
|
|
}
|
|
}
|
|
|
|
private synchronized boolean addHostAliasImpl(MappedHost newAlias) {
|
|
MappedHost[] newHosts = new MappedHost[hosts.length + 1];
|
|
if (insertMap(hosts, newHosts, newAlias)) {
|
|
hosts = newHosts;
|
|
if (newAlias.name.equals(defaultHostName)) {
|
|
defaultHost = newAlias;
|
|
}
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(sm.getString("mapper.addHostAlias.success",
|
|
newAlias.name, newAlias.getRealHostName()));
|
|
}
|
|
return true;
|
|
} else {
|
|
MappedHost duplicate = hosts[find(hosts, newAlias.name)];
|
|
if (duplicate.getRealHost() == newAlias.getRealHost()) {
|
|
// A duplicate Alias for the same Host.
|
|
// A harmless redundancy. E.g.
|
|
// <Host name="localhost"><Alias>localhost</Alias></Host>
|
|
if (log.isDebugEnabled()) {
|
|
log.debug(sm.getString("mapper.addHostAlias.sameHost",
|
|
newAlias.name, newAlias.getRealHostName()));
|
|
}
|
|
return false;
|
|
}
|
|
log.error(sm.getString("mapper.duplicateHostAlias", newAlias.name,
|
|
newAlias.getRealHostName(), duplicate.getRealHostName()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a host alias
|
|
* @param alias The alias to remove
|
|
*/
|
|
public synchronized void removeHostAlias(String alias) {
|
|
alias = renameWildcardHost(alias);
|
|
// Find and remove the alias
|
|
MappedHost hostMapping = exactFind(hosts, alias);
|
|
if (hostMapping == null || !hostMapping.isAlias()) {
|
|
return;
|
|
}
|
|
MappedHost[] newHosts = new MappedHost[hosts.length - 1];
|
|
if (removeMap(hosts, newHosts, alias)) {
|
|
hosts = newHosts;
|
|
hostMapping.getRealHost().removeAlias(hostMapping);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Replace {@link MappedHost#contextList} field in <code>realHost</code> and
|
|
* all its aliases with a new value.
|
|
*/
|
|
private void updateContextList(MappedHost realHost,
|
|
ContextList newContextList) {
|
|
|
|
realHost.contextList = newContextList;
|
|
for (MappedHost alias : realHost.getAliases()) {
|
|
alias.contextList = newContextList;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* @param wrappers Information on wrapper mappings
|
|
*/
|
|
public void addContextVersion(String hostName, Host host, String path,
|
|
String version, Context context, String[] welcomeResources,
|
|
WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {
|
|
|
|
hostName = renameWildcardHost(hostName);
|
|
|
|
MappedHost mappedHost = exactFind(hosts, hostName);
|
|
if (mappedHost == null) {
|
|
addHost(hostName, new String[0], host);
|
|
mappedHost = exactFind(hosts, hostName);
|
|
if (mappedHost == null) {
|
|
log.error("No host found: " + hostName);
|
|
return;
|
|
}
|
|
}
|
|
if (mappedHost.isAlias()) {
|
|
log.error("No host found: " + hostName);
|
|
return;
|
|
}
|
|
int slashCount = slashCount(path);
|
|
synchronized (mappedHost) {
|
|
ContextVersion newContextVersion = new ContextVersion(version,
|
|
path, slashCount, context, resources, welcomeResources);
|
|
if (wrappers != null) {
|
|
addWrappers(newContextVersion, wrappers);
|
|
}
|
|
|
|
ContextList contextList = mappedHost.contextList;
|
|
MappedContext mappedContext = exactFind(contextList.contexts, path);
|
|
if (mappedContext == null) {
|
|
mappedContext = new MappedContext(path, newContextVersion);
|
|
ContextList newContextList = contextList.addContext(
|
|
mappedContext, slashCount);
|
|
if (newContextList != null) {
|
|
updateContextList(mappedHost, newContextList);
|
|
contextObjectToContextVersionMap.put(context, newContextVersion);
|
|
}
|
|
} else {
|
|
ContextVersion[] contextVersions = mappedContext.versions;
|
|
ContextVersion[] newContextVersions = new ContextVersion[contextVersions.length + 1];
|
|
if (insertMap(contextVersions, newContextVersions,
|
|
newContextVersion)) {
|
|
mappedContext.versions = newContextVersions;
|
|
contextObjectToContextVersionMap.put(context, newContextVersion);
|
|
} else {
|
|
// Re-registration after Context.reload()
|
|
// Replace ContextVersion with the new one
|
|
int pos = find(contextVersions, version);
|
|
if (pos >= 0 && contextVersions[pos].name.equals(version)) {
|
|
contextVersions[pos] = newContextVersion;
|
|
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) {
|
|
|
|
hostName = renameWildcardHost(hostName);
|
|
contextObjectToContextVersionMap.remove(ctxt);
|
|
|
|
MappedHost host = exactFind(hosts, hostName);
|
|
if (host == null || host.isAlias()) {
|
|
return;
|
|
}
|
|
|
|
synchronized (host) {
|
|
ContextList contextList = host.contextList;
|
|
MappedContext context = exactFind(contextList.contexts, path);
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
|
|
ContextVersion[] contextVersions = context.versions;
|
|
ContextVersion[] newContextVersions =
|
|
new ContextVersion[contextVersions.length - 1];
|
|
if (removeMap(contextVersions, newContextVersions, version)) {
|
|
if (newContextVersions.length == 0) {
|
|
// Remove the context
|
|
ContextList newContextList = contextList.removeContext(path);
|
|
if (newContextList != null) {
|
|
updateContextList(host, newContextList);
|
|
}
|
|
} else {
|
|
context.versions = newContextVersions;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Mark a context as being reloaded. Reversion of this state is performed
|
|
* by calling <code>addContextVersion(...)</code> when context starts up.
|
|
*
|
|
* @param ctxt The actual context
|
|
* @param hostName Virtual host name this context belongs to
|
|
* @param contextPath Context path
|
|
* @param version Context version
|
|
*/
|
|
public void pauseContextVersion(Context ctxt, String hostName,
|
|
String contextPath, String version) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName,
|
|
contextPath, version, true);
|
|
if (contextVersion == null || !ctxt.equals(contextVersion.object)) {
|
|
return;
|
|
}
|
|
contextVersion.markPaused();
|
|
}
|
|
|
|
|
|
private ContextVersion findContextVersion(String hostName,
|
|
String contextPath, String version, boolean silent) {
|
|
MappedHost host = exactFind(hosts, hostName);
|
|
if (host == null || host.isAlias()) {
|
|
if (!silent) {
|
|
log.error("No host found: " + hostName);
|
|
}
|
|
return null;
|
|
}
|
|
MappedContext context = exactFind(host.contextList.contexts,
|
|
contextPath);
|
|
if (context == null) {
|
|
if (!silent) {
|
|
log.error("No context found: " + contextPath);
|
|
}
|
|
return null;
|
|
}
|
|
ContextVersion contextVersion = exactFind(context.versions, version);
|
|
if (contextVersion == null) {
|
|
if (!silent) {
|
|
log.error("No context version found: " + contextPath + " "
|
|
+ version);
|
|
}
|
|
return null;
|
|
}
|
|
return contextVersion;
|
|
}
|
|
|
|
|
|
public void addWrapper(String hostName, String contextPath, String version,
|
|
String path, Wrapper wrapper, boolean jspWildCard,
|
|
boolean resourceOnly) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName,
|
|
contextPath, version, false);
|
|
if (contextVersion == null) {
|
|
return;
|
|
}
|
|
addWrapper(contextVersion, path, wrapper, jspWildCard, resourceOnly);
|
|
}
|
|
|
|
public void addWrappers(String hostName, String contextPath,
|
|
String version, Collection<WrapperMappingInfo> wrappers) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName,
|
|
contextPath, version, false);
|
|
if (contextVersion == null) {
|
|
return;
|
|
}
|
|
addWrappers(contextVersion, wrappers);
|
|
}
|
|
|
|
/**
|
|
* Adds wrappers to the given context.
|
|
*
|
|
* @param contextVersion The context to which to add the wrappers
|
|
* @param wrappers Information on wrapper mappings
|
|
*/
|
|
private void addWrappers(ContextVersion contextVersion,
|
|
Collection<WrapperMappingInfo> wrappers) {
|
|
for (WrapperMappingInfo wrapper : wrappers) {
|
|
addWrapper(contextVersion, wrapper.getMapping(),
|
|
wrapper.getWrapper(), wrapper.isJspWildCard(),
|
|
wrapper.isResourceOnly());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* and the mapping path contains a wildcard; false otherwise
|
|
* @param resourceOnly true if this wrapper always expects a physical
|
|
* resource to be present (such as a JSP)
|
|
*/
|
|
protected void addWrapper(ContextVersion context, String path,
|
|
Wrapper wrapper, boolean jspWildCard, boolean resourceOnly) {
|
|
|
|
synchronized (context) {
|
|
if (path.endsWith("/*")) {
|
|
// Wildcard wrapper
|
|
String name = path.substring(0, path.length() - 2);
|
|
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
|
|
jspWildCard, resourceOnly);
|
|
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
|
|
String name = path.substring(2);
|
|
MappedWrapper newWrapper = new MappedWrapper(name, wrapper,
|
|
jspWildCard, resourceOnly);
|
|
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
|
|
MappedWrapper newWrapper = new MappedWrapper("", wrapper,
|
|
jspWildCard, resourceOnly);
|
|
context.defaultWrapper = newWrapper;
|
|
} else {
|
|
// Exact wrapper
|
|
final 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 newWrapper = new MappedWrapper(name, wrapper,
|
|
jspWildCard, resourceOnly);
|
|
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 version Context version this wrapper belongs to
|
|
* @param path Wrapper mapping
|
|
*/
|
|
public void removeWrapper(String hostName, String contextPath,
|
|
String version, String path) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName,
|
|
contextPath, version, true);
|
|
if (contextVersion == null || contextVersion.isPaused()) {
|
|
return;
|
|
}
|
|
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 The host where the given context can be found
|
|
* @param contextPath The path of the given context
|
|
* @param version The version of the given context
|
|
* @param welcomeFile The welcome file to add
|
|
*/
|
|
public void addWelcomeFile(String hostName, String contextPath, String version,
|
|
String welcomeFile) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false);
|
|
if (contextVersion == null) {
|
|
return;
|
|
}
|
|
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 The host where the given context can be found
|
|
* @param contextPath The path of the given context
|
|
* @param version The version of the given context
|
|
* @param welcomeFile The welcome file to remove
|
|
*/
|
|
public void removeWelcomeFile(String hostName, String contextPath,
|
|
String version, String welcomeFile) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false);
|
|
if (contextVersion == null || contextVersion.isPaused()) {
|
|
return;
|
|
}
|
|
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 The host where the context to be cleared can be found
|
|
* @param contextPath The path of the context to be cleared
|
|
* @param version The version of the context to be cleared
|
|
*/
|
|
public void clearWelcomeFiles(String hostName, String contextPath, String version) {
|
|
hostName = renameWildcardHost(hostName);
|
|
ContextVersion contextVersion = findContextVersion(hostName, contextPath, version, false);
|
|
if (contextVersion == null) {
|
|
return;
|
|
}
|
|
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 version The version, if any, included in the request to be mapped
|
|
* @param mappingData This structure will contain the result of the mapping
|
|
* operation
|
|
* @throws IOException if the buffers are too small to hold the results of
|
|
* the mapping.
|
|
*/
|
|
public void map(MessageBytes host, MessageBytes uri, String version,
|
|
MappingData mappingData) throws IOException {
|
|
|
|
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
|
|
* @throws IOException if the buffers are too small to hold the results of
|
|
* the mapping.
|
|
*/
|
|
public void map(Context context, MessageBytes uri,
|
|
MappingData mappingData) throws IOException {
|
|
|
|
ContextVersion contextVersion =
|
|
contextObjectToContextVersionMap.get(context);
|
|
uri.toChars();
|
|
CharChunk uricc = uri.getCharChunk();
|
|
uricc.setLimit(-1);
|
|
internalMapWrapper(contextVersion, uricc, mappingData);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------- Private Methods
|
|
|
|
/**
|
|
* Map the specified URI.
|
|
* @throws IOException
|
|
*/
|
|
@SuppressWarnings("deprecation") // contextPath
|
|
private final void internalMap(CharChunk host, CharChunk uri,
|
|
String version, MappingData mappingData) throws IOException {
|
|
|
|
if (mappingData.host != null) {
|
|
// The legacy code (dating down at least to Tomcat 4.1) just
|
|
// skipped all mapping work in this case. That behaviour has a risk
|
|
// of returning an inconsistent result.
|
|
// I do not see a valid use case for it.
|
|
throw new AssertionError();
|
|
}
|
|
|
|
// Virtual host mapping
|
|
MappedHost[] hosts = this.hosts;
|
|
MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
|
|
if (mappedHost == null) {
|
|
// Note: Internally, the Mapper does not use the leading * on a
|
|
// wildcard host. This is to allow this shortcut.
|
|
int firstDot = host.indexOf('.');
|
|
if (firstDot > -1) {
|
|
int offset = host.getOffset();
|
|
try {
|
|
host.setOffset(firstDot + offset);
|
|
mappedHost = exactFindIgnoreCase(hosts, host);
|
|
} finally {
|
|
// Make absolutely sure this gets reset
|
|
host.setOffset(offset);
|
|
}
|
|
}
|
|
if (mappedHost == null) {
|
|
mappedHost = defaultHost;
|
|
if (mappedHost == null) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
mappingData.host = mappedHost.object;
|
|
|
|
if (uri.isNull()) {
|
|
// Can't map context or wrapper without a uri
|
|
return;
|
|
}
|
|
|
|
uri.setLimit(-1);
|
|
|
|
// Context mapping
|
|
ContextList contextList = mappedHost.contextList;
|
|
MappedContext[] contexts = contextList.contexts;
|
|
int pos = find(contexts, uri);
|
|
if (pos == -1) {
|
|
return;
|
|
}
|
|
|
|
int lastSlash = -1;
|
|
int uriEnd = uri.getEnd();
|
|
int length = -1;
|
|
boolean found = false;
|
|
MappedContext context = null;
|
|
while (pos >= 0) {
|
|
context = contexts[pos];
|
|
if (uri.startsWith(context.name)) {
|
|
length = context.name.length();
|
|
if (uri.getLength() == length) {
|
|
found = true;
|
|
break;
|
|
} else if (uri.startsWithIgnoreCase("/", length)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (lastSlash == -1) {
|
|
lastSlash = nthSlash(uri, contextList.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 = null;
|
|
}
|
|
}
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
|
|
mappingData.contextPath.setString(context.name);
|
|
|
|
ContextVersion contextVersion = null;
|
|
ContextVersion[] contextVersions = context.versions;
|
|
final 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) {
|
|
contextVersion = exactFind(contextVersions, version);
|
|
}
|
|
}
|
|
if (contextVersion == null) {
|
|
// Return the latest version
|
|
// The versions array is known to contain at least one element
|
|
contextVersion = contextVersions[versionCount - 1];
|
|
}
|
|
mappingData.context = contextVersion.object;
|
|
mappingData.contextSlashCount = contextVersion.slashCount;
|
|
|
|
// Wrapper mapping
|
|
if (!contextVersion.isPaused()) {
|
|
internalMapWrapper(contextVersion, uri, mappingData);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Wrapper mapping.
|
|
* @throws IOException if the buffers are too small to hold the results of
|
|
* the mapping.
|
|
*/
|
|
private final void internalMapWrapper(ContextVersion contextVersion,
|
|
CharChunk path,
|
|
MappingData mappingData) throws IOException {
|
|
|
|
int pathOffset = path.getOffset();
|
|
int pathEnd = path.getEnd();
|
|
boolean noServletPath = false;
|
|
|
|
int length = contextVersion.path.length();
|
|
if (length == (pathEnd - pathOffset)) {
|
|
noServletPath = true;
|
|
}
|
|
int 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 &&
|
|
contextVersion.object.getMapperContextRootRedirectEnabled()) {
|
|
// The path is empty, redirect to "/"
|
|
path.append('/');
|
|
pathEnd = path.getEnd();
|
|
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());
|
|
mappingData.matchType = MappingMatch.DEFAULT;
|
|
}
|
|
// Redirection to a folder
|
|
char[] buf = path.getBuffer();
|
|
if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
|
|
String pathStr = path.toString();
|
|
// Note: Check redirect first to save unnecessary getResource()
|
|
// call. See BZ 62968.
|
|
if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {
|
|
WebResource file;
|
|
// Handle context root
|
|
if (pathStr.length() == 0) {
|
|
file = contextVersion.resources.getResource("/");
|
|
} else {
|
|
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);
|
|
}
|
|
} else {
|
|
mappingData.requestPath.setString(pathStr);
|
|
mappingData.wrapperPath.setString(pathStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
path.setOffset(pathOffset);
|
|
path.setEnd(pathEnd);
|
|
}
|
|
|
|
|
|
/**
|
|
* Exact mapping.
|
|
*/
|
|
@SuppressWarnings("deprecation") // contextPath
|
|
private final void internalMapExactWrapper
|
|
(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {
|
|
MappedWrapper wrapper = exactFind(wrappers, path);
|
|
if (wrapper != null) {
|
|
mappingData.requestPath.setString(wrapper.name);
|
|
mappingData.wrapper = wrapper.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("");
|
|
mappingData.matchType = MappingMatch.CONTEXT_ROOT;
|
|
} else {
|
|
mappingData.wrapperPath.setString(wrapper.name);
|
|
mappingData.matchType = MappingMatch.EXACT;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
mappingData.matchType = MappingMatch.PATH;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
MappedWrapper wrapper = exactFind(wrappers, path);
|
|
if (wrapper != null
|
|
&& (resourceExpected || !wrapper.resourceOnly)) {
|
|
mappingData.wrapperPath.setChars(buf, servletPath, pathEnd
|
|
- servletPath);
|
|
mappingData.requestPath.setChars(buf, servletPath, pathEnd
|
|
- servletPath);
|
|
mappingData.wrapper = wrapper.object;
|
|
mappingData.matchType = MappingMatch.EXTENSION;
|
|
}
|
|
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) >>> 1;
|
|
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) >>> 1;
|
|
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.
|
|
* @see #exactFind(MapElement[], String)
|
|
*/
|
|
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) >>> 1;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Find a map element given its name in a sorted array of map elements. This
|
|
* will return the element that you were searching for. Otherwise it will
|
|
* return <code>null</code>.
|
|
* @see #find(MapElement[], String)
|
|
*/
|
|
private static final <T, E extends MapElement<T>> E exactFind(E[] map,
|
|
String name) {
|
|
int pos = find(map, name);
|
|
if (pos >= 0) {
|
|
E result = map[pos];
|
|
if (name.equals(result.name)) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find a map element given its name in a sorted array of map elements. This
|
|
* will return the element that you were searching for. Otherwise it will
|
|
* return <code>null</code>.
|
|
*/
|
|
private static final <T, E extends MapElement<T>> E exactFind(E[] map,
|
|
CharChunk name) {
|
|
int pos = find(map, name);
|
|
if (pos >= 0) {
|
|
E result = map[pos];
|
|
if (name.equals(result.name)) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Find a map element given its name in a sorted array of map elements. This
|
|
* will return the element that you were searching for. Otherwise it will
|
|
* return <code>null</code>.
|
|
* @see #findIgnoreCase(MapElement[], CharChunk)
|
|
*/
|
|
private static final <T, E extends MapElement<T>> E exactFindIgnoreCase(
|
|
E[] map, CharChunk name) {
|
|
int pos = findIgnoreCase(map, name);
|
|
if (pos >= 0) {
|
|
E result = map[pos];
|
|
if (name.equalsIgnoreCase(result.name)) {
|
|
return result;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* To simplify the mapping process, wild card hosts take the form
|
|
* ".apache.org" rather than "*.apache.org" internally. However, for ease
|
|
* of use the external form remains "*.apache.org". Any host name passed
|
|
* into this class needs to be passed through this method to rename and
|
|
* wild card host names from the external to internal form.
|
|
*/
|
|
private static String renameWildcardHost(String hostName) {
|
|
if (hostName != null && hostName.startsWith("*.")) {
|
|
return hostName.substring(1);
|
|
} else {
|
|
return hostName;
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------- MapElement Inner Class
|
|
|
|
|
|
protected abstract static class MapElement<T> {
|
|
|
|
public final String name;
|
|
public final T object;
|
|
|
|
public MapElement(String name, T object) {
|
|
this.name = name;
|
|
this.object = object;
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------- Host Inner Class
|
|
|
|
|
|
protected static final class MappedHost extends MapElement<Host> {
|
|
|
|
public volatile ContextList contextList;
|
|
|
|
/**
|
|
* Link to the "real" MappedHost, shared by all aliases.
|
|
*/
|
|
private final MappedHost realHost;
|
|
|
|
/**
|
|
* Links to all registered aliases, for easy enumeration. This field
|
|
* is available only in the "real" MappedHost. In an alias this field
|
|
* is <code>null</code>.
|
|
*/
|
|
private final List<MappedHost> aliases;
|
|
|
|
/**
|
|
* Constructor used for the primary Host
|
|
*
|
|
* @param name The name of the virtual host
|
|
* @param host The host
|
|
*/
|
|
public MappedHost(String name, Host host) {
|
|
super(name, host);
|
|
realHost = this;
|
|
contextList = new ContextList();
|
|
aliases = new CopyOnWriteArrayList<>();
|
|
}
|
|
|
|
/**
|
|
* Constructor used for an Alias
|
|
*
|
|
* @param alias The alias of the virtual host
|
|
* @param realHost The host the alias points to
|
|
*/
|
|
public MappedHost(String alias, MappedHost realHost) {
|
|
super(alias, realHost.object);
|
|
this.realHost = realHost;
|
|
this.contextList = realHost.contextList;
|
|
this.aliases = null;
|
|
}
|
|
|
|
public boolean isAlias() {
|
|
return realHost != this;
|
|
}
|
|
|
|
public MappedHost getRealHost() {
|
|
return realHost;
|
|
}
|
|
|
|
public String getRealHostName() {
|
|
return realHost.name;
|
|
}
|
|
|
|
public Collection<MappedHost> getAliases() {
|
|
return aliases;
|
|
}
|
|
|
|
public void addAlias(MappedHost alias) {
|
|
aliases.add(alias);
|
|
}
|
|
|
|
public void addAliases(Collection<? extends MappedHost> c) {
|
|
aliases.addAll(c);
|
|
}
|
|
|
|
public void removeAlias(MappedHost alias) {
|
|
aliases.remove(alias);
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------ ContextList Inner Class
|
|
|
|
|
|
protected static final class ContextList {
|
|
|
|
public final MappedContext[] contexts;
|
|
public final int nesting;
|
|
|
|
public ContextList() {
|
|
this(new MappedContext[0], 0);
|
|
}
|
|
|
|
private ContextList(MappedContext[] contexts, int nesting) {
|
|
this.contexts = contexts;
|
|
this.nesting = nesting;
|
|
}
|
|
|
|
public ContextList addContext(MappedContext mappedContext,
|
|
int slashCount) {
|
|
MappedContext[] newContexts = new MappedContext[contexts.length + 1];
|
|
if (insertMap(contexts, newContexts, mappedContext)) {
|
|
return new ContextList(newContexts, Math.max(nesting,
|
|
slashCount));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public ContextList removeContext(String path) {
|
|
MappedContext[] newContexts = new MappedContext[contexts.length - 1];
|
|
if (removeMap(contexts, newContexts, path)) {
|
|
int newNesting = 0;
|
|
for (MappedContext context : newContexts) {
|
|
newNesting = Math.max(newNesting, slashCount(context.name));
|
|
}
|
|
return new ContextList(newContexts, newNesting);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------- Context Inner Class
|
|
|
|
|
|
protected static final class MappedContext extends MapElement<Void> {
|
|
public volatile ContextVersion[] versions;
|
|
|
|
public MappedContext(String name, ContextVersion firstVersion) {
|
|
super(name, null);
|
|
this.versions = new ContextVersion[] { firstVersion };
|
|
}
|
|
}
|
|
|
|
protected static final class ContextVersion extends MapElement<Context> {
|
|
public final String path;
|
|
public final int slashCount;
|
|
public final WebResourceRoot resources;
|
|
public String[] welcomeResources;
|
|
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;
|
|
private volatile boolean paused;
|
|
|
|
public ContextVersion(String version, String path, int slashCount,
|
|
Context context, WebResourceRoot resources,
|
|
String[] welcomeResources) {
|
|
super(version, context);
|
|
this.path = path;
|
|
this.slashCount = slashCount;
|
|
this.resources = resources;
|
|
this.welcomeResources = welcomeResources;
|
|
}
|
|
|
|
public boolean isPaused() {
|
|
return paused;
|
|
}
|
|
|
|
public void markPaused() {
|
|
paused = true;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------- Wrapper Inner Class
|
|
|
|
|
|
protected static class MappedWrapper extends MapElement<Wrapper> {
|
|
|
|
public final boolean jspWildCard;
|
|
public final boolean resourceOnly;
|
|
|
|
public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,
|
|
boolean resourceOnly) {
|
|
super(name, wrapper);
|
|
this.jspWildCard = jspWildCard;
|
|
this.resourceOnly = resourceOnly;
|
|
}
|
|
}
|
|
}
|