org.eclipse.jetty.webapp.StandardDescriptorProcessor Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.webapp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.SessionTrackingMode;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.security.ConstraintAware;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.servlet.Source;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.xml.XmlParser;
import org.eclipse.jetty.xml.XmlParser.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* StandardDescriptorProcessor.
*
* Process the web.xml, web-defaults.xml, web-overrides.xml, and web-fragment.xml descriptors.
*/
public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{
private static final Logger LOG = LoggerFactory.getLogger(StandardDescriptorProcessor.class);
public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
final Map _filterHolderMap = new HashMap<>();
final List _filterHolders = new ArrayList<>();
final List _filterMappings = new ArrayList<>();
final Map _servletHolderMap = new HashMap<>();
final List _servletHolders = new ArrayList<>();
final List _servletMappings = new ArrayList<>();
public StandardDescriptorProcessor()
{
try
{
registerVisitor("context-param", this.getClass().getMethod("visitContextParam", __signature));
registerVisitor("display-name", this.getClass().getMethod("visitDisplayName", __signature));
registerVisitor("servlet", this.getClass().getMethod("visitServlet", __signature));
registerVisitor("servlet-mapping", this.getClass().getMethod("visitServletMapping", __signature));
registerVisitor("session-config", this.getClass().getMethod("visitSessionConfig", __signature));
registerVisitor("mime-mapping", this.getClass().getMethod("visitMimeMapping", __signature));
registerVisitor("welcome-file-list", this.getClass().getMethod("visitWelcomeFileList", __signature));
registerVisitor("locale-encoding-mapping-list", this.getClass().getMethod("visitLocaleEncodingList", __signature));
registerVisitor("error-page", this.getClass().getMethod("visitErrorPage", __signature));
registerVisitor("taglib", this.getClass().getMethod("visitTagLib", __signature));
registerVisitor("jsp-config", this.getClass().getMethod("visitJspConfig", __signature));
registerVisitor("security-constraint", this.getClass().getMethod("visitSecurityConstraint", __signature));
registerVisitor("login-config", this.getClass().getMethod("visitLoginConfig", __signature));
registerVisitor("security-role", this.getClass().getMethod("visitSecurityRole", __signature));
registerVisitor("filter", this.getClass().getMethod("visitFilter", __signature));
registerVisitor("filter-mapping", this.getClass().getMethod("visitFilterMapping", __signature));
registerVisitor("listener", this.getClass().getMethod("visitListener", __signature));
registerVisitor("deny-uncovered-http-methods", this.getClass().getMethod("visitDenyUncoveredHttpMethods", __signature));
registerVisitor("default-context-path", this.getClass().getMethod("visitDefaultContextPath", __signature));
registerVisitor("request-character-encoding", this.getClass().getMethod("visitRequestCharacterEncoding", __signature));
registerVisitor("response-character-encoding", this.getClass().getMethod("visitResponseCharacterEncoding", __signature));
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void start(WebAppContext context, Descriptor descriptor)
{
for (FilterHolder h : context.getServletHandler().getFilters())
{
_filterHolderMap.put(h.getName(), h);
_filterHolders.add(h);
}
if (context.getServletHandler().getFilterMappings() != null)
_filterMappings.addAll(Arrays.asList(context.getServletHandler().getFilterMappings()));
for (ServletHolder h : context.getServletHandler().getServlets())
{
_servletHolderMap.put(h.getName(), h);
_servletHolders.add(h);
}
if (context.getServletHandler().getServletMappings() != null)
_servletMappings.addAll(Arrays.asList(context.getServletHandler().getServletMappings()));
}
/**
* {@inheritDoc}
*/
@Override
public void end(WebAppContext context, Descriptor descriptor)
{
context.getServletHandler().setFilters(_filterHolders.toArray(new FilterHolder[_filterHolderMap.size()]));
context.getServletHandler().setServlets(_servletHolders.toArray(new ServletHolder[_servletHolderMap.size()]));
context.getServletHandler().setFilterMappings(_filterMappings.toArray(new FilterMapping[_filterMappings.size()]));
context.getServletHandler().setServletMappings(_servletMappings.toArray(new ServletMapping[_servletMappings.size()]));
_filterHolderMap.clear();
_filterHolders.clear();
_filterMappings.clear();
_servletHolderMap.clear();
_servletHolders.clear();
_servletMappings.clear();
}
public void visitContextParam(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String name = node.getString("param-name", false, true);
String value = node.getString("param-value", false, true);
Origin origin = context.getMetaData().getOrigin("context-param." + name);
switch (origin)
{
case NotSet:
{
//just set it
context.getInitParams().put(name, value);
context.getMetaData().setOrigin("context-param." + name, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//previously set by a web xml, allow other web xml files to override
if (!(descriptor instanceof FragmentDescriptor))
{
context.getInitParams().put(name, value);
context.getMetaData().setOrigin("context-param." + name, descriptor);
}
break;
}
case WebFragment:
{
//previously set by a web-fragment, this fragment's value must be the same
if (descriptor instanceof FragmentDescriptor)
{
if (!((String)context.getInitParams().get(name)).equals(value))
throw new IllegalStateException("Conflicting context-param " + name + "=" + value + " in " + descriptor.getResource());
}
break;
}
default:
unknownOrigin(origin);
}
if (LOG.isDebugEnabled())
LOG.debug("ContextParam: {}={}", name, value);
}
public void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//Servlet Spec 3.0 p. 74 Ignore from web-fragments
if (!(descriptor instanceof FragmentDescriptor))
{
context.setDisplayName(node.toString(false, true));
context.getMetaData().setOrigin("display-name", descriptor);
}
}
public void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
final String id = node.getAttribute("id");
final String name = node.getString("servlet-name", false, true);
ServletHolder holder = _servletHolderMap.get(name);
//If servlet of that name does not already exist, create it.
if (holder == null)
{
holder = context.getServletHandler().newServletHolder(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
holder.setName(name);
_servletHolderMap.put(name, holder);
_servletHolders.add(holder);
}
// init params
Iterator> iParamsIter = node.iterator("init-param");
while (iParamsIter.hasNext())
{
XmlParser.Node paramNode = (XmlParser.Node)iParamsIter.next();
String pname = paramNode.getString("param-name", false, true);
String pvalue = paramNode.getString("param-value", false, true);
String originName = name + ".servlet.init-param." + pname;
Descriptor originDescriptor = context.getMetaData().getOriginDescriptor(originName);
Origin origin = context.getMetaData().getOrigin(originName);
switch (origin)
{
case NotSet:
{
//init-param not already set, so set it
holder.setInitParameter(pname, pvalue);
context.getMetaData().setOrigin(originName, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override as long as it is from a different descriptor
//ie ignore setting more than once within the same descriptor
//otherwise just ignore it
if (!(descriptor instanceof FragmentDescriptor) && (descriptor != originDescriptor))
{
holder.setInitParameter(pname, pvalue);
context.getMetaData().setOrigin(originName, descriptor);
}
break;
}
case WebFragment:
{
//previously set by a web-fragment, make sure that the value matches, otherwise its an error
if ((descriptor != originDescriptor) && !holder.getInitParameter(pname).equals(pvalue))
throw new IllegalStateException("Mismatching init-param " + pname + "=" + pvalue + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
String servletClass = node.getString("servlet-class", false, true);
if ("".equals(servletClass))
servletClass = null;
//Handle the default jsp servlet instance
if (id != null && id.equals("jsp") && servletClass != null)
{
try
{
Loader.loadClass(servletClass);
}
catch (ClassNotFoundException e)
{
LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servletClass);
servletClass = "org.eclipse.jetty.servlet.NoJspServlet";
}
}
//Set the servlet-class
if (servletClass != null)
{
((WebDescriptor)descriptor).addClassName(servletClass);
Origin origin = context.getMetaData().getOrigin(name + ".servlet.servlet-class");
switch (origin)
{
case NotSet:
{
//the class of the servlet has not previously been set, so set it
holder.setClassName(servletClass);
context.getMetaData().setOrigin(name + ".servlet.servlet-class", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//the class of the servlet was set by a web xml file, only allow web-override/web-default to change it
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setClassName(servletClass);
context.getMetaData().setOrigin(name + ".servlet.servlet-class", descriptor);
}
break;
}
case WebFragment:
{
//the class was set by another fragment, ensure this fragment's value is the same
if (!servletClass.equals(holder.getClassName()))
throw new IllegalStateException("Conflicting servlet-class " + servletClass + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
// Handle JSP file
String jspFile = node.getString("jsp-file", false, true);
if (jspFile != null)
holder.setForcedPath(jspFile);
// handle load-on-startup
XmlParser.Node startup = node.get("load-on-startup");
if (startup != null)
{
String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
int order = 0;
if (s.startsWith("t"))
{
LOG.warn("Deprecated boolean load-on-startup. Please use integer");
order = 1;
}
else
{
try
{
if (s != null && s.trim().length() > 0)
order = Integer.parseInt(s);
}
catch (Exception e)
{
LOG.warn("Cannot parse load-on-startup {}. Please use integer", s);
LOG.trace("IGNORED", e);
}
}
Origin origin = context.getMetaData().getOrigin(name + ".servlet.load-on-startup");
switch (origin)
{
case NotSet:
{
//not already set, so set it now
holder.setInitOrder(order);
context.getMetaData().setOrigin(name + ".servlet.load-on-startup", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setInitOrder(order);
context.getMetaData().setOrigin(name + ".servlet.load-on-startup", descriptor);
}
break;
}
case WebFragment:
{
//it was already set by another fragment, if we're parsing a fragment, the values must match
if (order != holder.getInitOrder())
throw new IllegalStateException("Conflicting load-on-startup value in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
Iterator sRefsIter = node.iterator("security-role-ref");
while (sRefsIter.hasNext())
{
XmlParser.Node securityRef = (XmlParser.Node)sRefsIter.next();
String roleName = securityRef.getString("role-name", false, true);
String roleLink = securityRef.getString("role-link", false, true);
if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
{
if (LOG.isDebugEnabled())
LOG.debug("link role {} to {} for {}", roleName, roleLink, this);
Origin origin = context.getMetaData().getOrigin(name + ".servlet.role-name." + roleName);
switch (origin)
{
case NotSet:
{
//set it
holder.setUserRoleLink(roleName, roleLink);
context.getMetaData().setOrigin(name + ".servlet.role-name." + roleName, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//only another web xml descriptor (web-default,web-override web.xml) can override an already set value
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setUserRoleLink(roleName, roleLink);
context.getMetaData().setOrigin(name + ".servlet.role-name." + roleName, descriptor);
}
break;
}
case WebFragment:
{
if (!holder.getUserRoleLink(roleName).equals(roleLink))
throw new IllegalStateException("Conflicting role-link for role-name " + roleName + " for servlet " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
else
{
LOG.warn("Ignored invalid security-role-ref element: servlet-name={}, {}", holder.getName(), securityRef);
}
}
XmlParser.Node runAs = node.get("run-as");
if (runAs != null)
{
String roleName = runAs.getString("role-name", false, true);
if (roleName != null)
{
Origin origin = context.getMetaData().getOrigin(name + ".servlet.run-as");
switch (origin)
{
case NotSet:
{
//run-as not set, so set it
holder.setRunAsRole(roleName);
context.getMetaData().setOrigin(name + ".servlet.run-as", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//run-as was set by a web xml, only allow it to be changed if we're currently parsing another web xml(override/default)
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setRunAsRole(roleName);
context.getMetaData().setOrigin(name + ".servlet.run-as", descriptor);
}
break;
}
case WebFragment:
{
//run-as was set by another fragment, this fragment must show the same value
if (!holder.getRunAsRole().equals(roleName))
throw new IllegalStateException("Conflicting run-as role " + roleName + " for servlet " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
String async = node.getString("async-supported", false, true);
if (async != null)
{
boolean val = async.length() == 0 || Boolean.parseBoolean(async);
Origin origin = context.getMetaData().getOrigin(name + ".servlet.async-supported");
switch (origin)
{
case NotSet:
{
//set it
holder.setAsyncSupported(val);
context.getMetaData().setOrigin(name + ".servlet.async-supported", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setAsyncSupported(val);
context.getMetaData().setOrigin(name + ".servlet.async-supported", descriptor);
}
break;
}
case WebFragment:
{
//async-supported set by another fragment, this fragment's value must match
if (holder.isAsyncSupported() != val)
throw new IllegalStateException("Conflicting async-supported=" + async + " for servlet " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
String enabled = node.getString("enabled", false, true);
if (enabled != null)
{
boolean isEnabled = enabled.length() == 0 || Boolean.parseBoolean(enabled);
Origin origin = context.getMetaData().getOrigin(name + ".servlet.enabled");
switch (origin)
{
case NotSet:
{
//hasn't been set yet, so set it
holder.setEnabled(isEnabled);
context.getMetaData().setOrigin(name + ".servlet.enabled", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//was set in a web xml descriptor, only allow override from another web xml descriptor
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setEnabled(isEnabled);
context.getMetaData().setOrigin(name + ".servlet.enabled", descriptor);
}
break;
}
case WebFragment:
{
//was set by another fragment, this fragment's value must match
if (holder.isEnabled() != isEnabled)
throw new IllegalStateException("Conflicting value of servlet enabled for servlet " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
/*
* If multipart config not set, then set it and record it was by the web.xml or fragment.
* If it was set by web.xml then if this is a fragment, ignore the settings.
* If it was set by a fragment, if this is a fragment and the values are different, error!
*/
XmlParser.Node multipart = node.get("multipart-config");
if (multipart != null)
{
String location = multipart.getString("location", false, true);
String maxFile = multipart.getString("max-file-size", false, true);
String maxRequest = multipart.getString("max-request-size", false, true);
String threshold = multipart.getString("file-size-threshold", false, true);
MultipartConfigElement element = new MultipartConfigElement(location,
(maxFile == null || "".equals(maxFile) ? -1L : Long.parseLong(maxFile)),
(maxRequest == null || "".equals(maxRequest) ? -1L : Long.parseLong(maxRequest)),
(threshold == null || "".equals(threshold) ? 0 : Integer.parseInt(threshold)));
Origin origin = context.getMetaData().getOrigin(name + ".servlet.multipart-config");
switch (origin)
{
case NotSet:
{
//hasn't been set, so set it
holder.getRegistration().setMultipartConfig(element);
context.getMetaData().setOrigin(name + ".servlet.multipart-config", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//was set in a web xml, only allow changes if we're parsing another web xml (web.xml/web-default.xml/web-override.xml)
if (!(descriptor instanceof FragmentDescriptor))
{
holder.getRegistration().setMultipartConfig(element);
context.getMetaData().setOrigin(name + ".servlet.multipart-config", descriptor);
}
break;
}
case WebFragment:
{
//another fragment set the value, this fragment's values must match exactly or it is an error
MultipartConfigElement cfg = ((ServletHolder.Registration)holder.getRegistration()).getMultipartConfig();
if (cfg.getMaxFileSize() != element.getMaxFileSize())
throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet " + name + " in " + descriptor.getResource());
if (cfg.getMaxRequestSize() != element.getMaxRequestSize())
throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet " + name + " in " + descriptor.getResource());
if (cfg.getFileSizeThreshold() != element.getFileSizeThreshold())
throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet " + name + " in " + descriptor.getResource());
if ((cfg.getLocation() != null && (element.getLocation() == null || element.getLocation().length() == 0)) ||
(cfg.getLocation() == null && (element.getLocation() != null || element.getLocation().length() > 0)))
throw new IllegalStateException("Conflicting multipart-config location for servlet " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
public void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//Servlet Spec 3.0, p74
//servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
//Maintenance update 3.0a to spec:
// Updated 8.2.3.g.v to say elements are additive across web-fragments.
// declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
String servletName = node.getString("servlet-name", false, true);
Origin origin = context.getMetaData().getOrigin(servletName + ".servlet.mappings");
switch (origin)
{
case NotSet:
{
//no servlet mappings
context.getMetaData().setOrigin(servletName + ".servlet.mappings", descriptor);
addServletMapping(servletName, node, context, descriptor);
break;
}
case WebDefaults:
case WebXml:
case WebOverride:
{
//previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
//otherwise just ignore it as web.xml takes precedence (pg 8-81 5.g.vi)
if (!(descriptor instanceof FragmentDescriptor))
{
addServletMapping(servletName, node, context, descriptor);
}
break;
}
case WebFragment:
{
//mappings previously set by another web-fragment, so merge in this web-fragment's mappings
addServletMapping(servletName, node, context, descriptor);
break;
}
default:
unknownOrigin(origin);
}
}
public void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
if (context.getSessionHandler() == null)
return; //no session handler, ignore session setup
XmlParser.Node tNode = node.get("session-timeout");
if (tNode != null)
{
long mins = Long.parseLong(tNode.toString(false, true));
if (TimeUnit.MINUTES.toSeconds(mins) > Integer.MAX_VALUE)
throw new IllegalStateException("Max session-timeout in minutes is " + TimeUnit.SECONDS.toMinutes(Integer.MAX_VALUE));
context.getServletContext().setSessionTimeout((int)mins);
context.getMetaData().setOrigin("session.timeout", descriptor);
}
//Servlet Spec 3.0
//
// this is additive across web-fragments
Iterator iter = node.iterator("tracking-mode");
if (iter.hasNext())
{
Set modes = null;
Origin o = context.getMetaData().getOrigin("session.tracking-modes");
switch (o)
{
case NotSet://not previously set, starting fresh
case WebDefaults://previously set in web defaults, allow this descriptor to start fresh
{
modes = new HashSet();
context.getMetaData().setOrigin("session.tracking-modes", descriptor);
break;
}
case WebXml:
case WebFragment:
case WebOverride:
{
//if setting from an override descriptor, start afresh, otherwise add-in tracking-modes
if (descriptor instanceof OverrideDescriptor)
modes = new HashSet();
else
modes = new HashSet(context.getSessionHandler().getEffectiveSessionTrackingModes());
context.getMetaData().setOrigin("session.tracking-modes", descriptor);
break;
}
default:
unknownOrigin(o);
}
while (iter.hasNext())
{
XmlParser.Node mNode = (XmlParser.Node)iter.next();
String trackMode = mNode.toString(false, true);
SessionTrackingMode mode = SessionTrackingMode.valueOf(trackMode);
modes.add(mode);
context.getMetaData().setOrigin("session.tracking-mode." + mode, descriptor);
}
context.getSessionHandler().setSessionTrackingModes(modes);
}
//Servlet Spec 3.0
//
XmlParser.Node cookieConfig = node.get("cookie-config");
if (cookieConfig != null)
{
//
String name = cookieConfig.getString("name", false, true);
if (name != null)
{
Origin origin = context.getMetaData().getOrigin("cookie-config.name");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setName(name);
context.getMetaData().setOrigin("cookie-config.name", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setName(name);
context.getMetaData().setOrigin("cookie-config.name", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (!name.equals(SessionHandler.getSessionCookieName(context.getSessionHandler().getSessionCookieConfig())))
throw new IllegalStateException("Conflicting cookie-config name " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
//
String domain = cookieConfig.getString("domain", false, true);
if (domain != null)
{
Origin origin = context.getMetaData().getOrigin("cookie-config.domain");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setDomain(domain);
context.getMetaData().setOrigin("cookie-config.domain", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setDomain(domain);
context.getMetaData().setOrigin("cookie-config.domain", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (!context.getSessionHandler().getSessionCookieConfig().getDomain().equals(domain))
throw new IllegalStateException("Conflicting cookie-config domain " + domain + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
//
String path = cookieConfig.getString("path", false, true);
if (path != null)
{
Origin origin = context.getMetaData().getOrigin("cookie-config.path");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setPath(path);
context.getMetaData().setOrigin("cookie-config.path", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setPath(path);
context.getMetaData().setOrigin("cookie-config.path", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (!path.equals(context.getSessionHandler().getSessionCookieConfig().getPath()))
throw new IllegalStateException("Conflicting cookie-config path " + path + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
//
String comment = cookieConfig.getString("comment", false, true);
if (comment != null)
{
Origin origin = context.getMetaData().getOrigin("cookie-config.comment");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setComment(comment);
context.getMetaData().setOrigin("cookie-config.comment", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setComment(comment);
context.getMetaData().setOrigin("cookie-config.comment", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (!context.getSessionHandler().getSessionCookieConfig().getComment().equals(comment))
throw new IllegalStateException("Conflicting cookie-config comment " + comment + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
// true/false
tNode = cookieConfig.get("http-only");
if (tNode != null)
{
boolean httpOnly = Boolean.parseBoolean(tNode.toString(false, true));
Origin origin = context.getMetaData().getOrigin("cookie-config.http-only");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setHttpOnly(httpOnly);
context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setHttpOnly(httpOnly);
context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (context.getSessionHandler().getSessionCookieConfig().isHttpOnly() != httpOnly)
throw new IllegalStateException("Conflicting cookie-config http-only " + httpOnly + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
// true/false
tNode = cookieConfig.get("secure");
if (tNode != null)
{
boolean secure = Boolean.parseBoolean(tNode.toString(false, true));
Origin origin = context.getMetaData().getOrigin("cookie-config.secure");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setSecure(secure);
context.getMetaData().setOrigin("cookie-config.secure", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setSecure(secure);
context.getMetaData().setOrigin("cookie-config.secure", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (context.getSessionHandler().getSessionCookieConfig().isSecure() != secure)
throw new IllegalStateException("Conflicting cookie-config secure " + secure + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
//
tNode = cookieConfig.get("max-age");
if (tNode != null)
{
int maxAge = Integer.parseInt(tNode.toString(false, true));
Origin origin = context.getMetaData().getOrigin("cookie-config.max-age");
switch (origin)
{
case NotSet:
{
//no set yet, accept it
context.getSessionHandler().getSessionCookieConfig().setMaxAge(maxAge);
context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
// set in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSessionHandler().getSessionCookieConfig().setMaxAge(maxAge);
context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (context.getSessionHandler().getSessionCookieConfig().getMaxAge() != maxAge)
throw new IllegalStateException("Conflicting cookie-config max-age " + maxAge + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
}
public void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String extension = node.getString("extension", false, true);
if (extension != null && extension.startsWith("."))
extension = extension.substring(1);
String mimeType = node.getString("mime-type", false, true);
if (extension != null)
{
Origin origin = context.getMetaData().getOrigin("extension." + extension);
switch (origin)
{
case NotSet:
{
//no mime-type set for the extension yet
context.getMimeTypes().addMimeMapping(extension, mimeType);
context.getMetaData().setOrigin("extension." + extension, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//a mime-type was set for the extension in a web xml, only allow web-default/web-override to change
if (!(descriptor instanceof FragmentDescriptor))
{
context.getMimeTypes().addMimeMapping(extension, mimeType);
context.getMetaData().setOrigin("extension." + extension, descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment set the value, all web-fragments must have the same value
if (!context.getMimeTypes().getMimeByExtension("." + extension).equals(mimeType))
throw new IllegalStateException("Conflicting mime-type " + mimeType + " for extension " + extension + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
public void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
Origin origin = context.getMetaData().getOrigin("welcome-file-list");
switch (origin)
{
case NotSet:
{
context.getMetaData().setOrigin("welcome-file-list", descriptor);
addWelcomeFiles(context, node, descriptor);
break;
}
case WebXml:
{
//web.xml set the welcome-file-list, all other descriptors then just merge in
addWelcomeFiles(context, node, descriptor);
break;
}
case WebDefaults:
{
//if web-defaults set the welcome-file-list first and
//we're processing web.xml then reset the welcome-file-list
if (!(descriptor instanceof DefaultsDescriptor) && !(descriptor instanceof OverrideDescriptor) && !(descriptor instanceof FragmentDescriptor))
{
context.setWelcomeFiles(new String[0]);
}
addWelcomeFiles(context, node, descriptor);
break;
}
case WebOverride:
{
//web-override set the list, all other descriptors just merge in
addWelcomeFiles(context, node, descriptor);
break;
}
case WebFragment:
{
//A web-fragment first set the welcome-file-list. Other descriptors just add.
addWelcomeFiles(context, node, descriptor);
break;
}
default:
unknownOrigin(origin);
}
}
public void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
Iterator iter = node.iterator("locale-encoding-mapping");
while (iter.hasNext())
{
XmlParser.Node mapping = iter.next();
String locale = mapping.getString("locale", false, true);
String encoding = mapping.getString("encoding", false, true);
if (encoding != null)
{
Origin origin = context.getMetaData().getOrigin("locale-encoding." + locale);
switch (origin)
{
case NotSet:
{
//no mapping for the locale yet, so set it
context.addLocaleEncoding(locale, encoding);
context.getMetaData().setOrigin("locale-encoding." + locale, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//a value was set in a web descriptor, only allow another web descriptor to change it (web-default/web-override)
if (!(descriptor instanceof FragmentDescriptor))
{
context.addLocaleEncoding(locale, encoding);
context.getMetaData().setOrigin("locale-encoding." + locale, descriptor);
}
break;
}
case WebFragment:
{
//a value was set by a web-fragment, all fragments must have the same value
if (!encoding.equals(context.getLocaleEncoding(locale)))
throw new IllegalStateException("Conflicting loacle-encoding mapping for locale " + locale + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
}
public void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String error = node.getString("error-code", false, true);
int code = 0;
if (error == null || error.length() == 0)
{
error = node.getString("exception-type", false, true);
if (error == null || error.length() == 0)
error = ErrorPageErrorHandler.GLOBAL_ERROR_PAGE;
}
else
code = Integer.parseInt(error);
String location = node.getString("location", false, true);
if (!location.startsWith("/"))
throw new IllegalStateException("Missing leading '/' for location: " + location);
ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
String originName = "error." + error;
Origin origin = context.getMetaData().getOrigin(originName);
switch (origin)
{
case NotSet:
{
//no error page setup for this code or exception yet
if (code > 0)
handler.addErrorPage(code, location);
else
handler.addErrorPage(error, location);
context.getMetaData().setOrigin("error." + error, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//an error page setup was set in web.xml/webdefault.xml/web-override.xml, only allow other web xml descriptors to override it
if (!(descriptor instanceof FragmentDescriptor))
{
//if set twice in the same descriptor, its an error
Descriptor originDescriptor = context.getMetaData().getOriginDescriptor(originName);
if (descriptor == originDescriptor)
throw new IllegalStateException("Duplicate error-page " + error + " at " + location);
if (code > 0)
handler.addErrorPage(code, location);
else
handler.addErrorPage(error, location);
context.getMetaData().setOrigin("error." + error, descriptor);
}
break;
}
case WebFragment:
{
//another web fragment set the same error code or exception, if its different its an error
if (!handler.getErrorPages().get(error).equals(location))
throw new IllegalStateException("Conflicting error-code or exception-type " + error + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
public void addWelcomeFiles(WebAppContext context, XmlParser.Node node, Descriptor descriptor)
{
Iterator iter = node.iterator("welcome-file");
while (iter.hasNext())
{
XmlParser.Node indexNode = (XmlParser.Node)iter.next();
String welcome = indexNode.toString(false, true);
context.getMetaData().setOrigin("welcome-file." + welcome, descriptor);
//Servlet Spec 3.0 p. 74 welcome files are additive
if (welcome != null && welcome.trim().length() > 0)
context.setWelcomeFiles((String[])ArrayUtil.addToArray(context.getWelcomeFiles(), welcome, String.class));
}
}
public ServletMapping addServletMapping(String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
{
ServletMapping mapping = new ServletMapping(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
mapping.setServletName(servletName);
mapping.setFromDefaultDescriptor(descriptor instanceof DefaultsDescriptor);
context.getMetaData().setOrigin(servletName + ".servlet.mapping." + Long.toHexString(mapping.hashCode()), descriptor);
List paths = new ArrayList();
Iterator iter = node.iterator("url-pattern");
while (iter.hasNext())
{
String p = iter.next().toString(false, true);
p = ServletPathSpec.normalize(p);
//check if there is already a mapping for this path
ListIterator listItor = _servletMappings.listIterator();
boolean found = false;
while (listItor.hasNext() && !found)
{
ServletMapping sm = listItor.next();
if (sm.getPathSpecs() != null)
{
for (String ps : sm.getPathSpecs())
{
//The same path has been mapped multiple times, either to a different servlet or the same servlet.
//If its a different servlet, this is only valid to do if the old mapping was from a default descriptor.
if (p.equals(ps) && (sm.isFromDefaultDescriptor() || servletName.equals(sm.getServletName())))
{
if (sm.isFromDefaultDescriptor())
{
if (LOG.isDebugEnabled())
LOG.debug("{} in mapping {} from defaults descriptor is overridden by {}", ps, sm, servletName);
}
else
LOG.warn("Duplicate mapping from {} to {}", p, servletName);
//remove ps from the path specs on the existing mapping
//if the mapping now has no pathspecs, remove it
String[] updatedPaths = ArrayUtil.removeFromArray(sm.getPathSpecs(), ps);
if (updatedPaths == null || updatedPaths.length == 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Removed empty mapping {}", sm);
listItor.remove();
}
else
{
sm.setPathSpecs(updatedPaths);
if (LOG.isDebugEnabled())
LOG.debug("Removed path {} from mapping {}", p, sm);
}
found = true;
break;
}
}
}
}
paths.add(p);
context.getMetaData().setOrigin(servletName + ".servlet.mapping.url" + p, descriptor);
}
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
if (LOG.isDebugEnabled())
LOG.debug("Added mapping {} ", mapping);
_servletMappings.add(mapping);
return mapping;
}
public void addFilterMapping(String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
{
FilterMapping mapping = new FilterMapping();
mapping.setFilterName(filterName);
context.getMetaData().setOrigin(filterName + ".filter.mapping." + Long.toHexString(mapping.hashCode()), descriptor);
List paths = new ArrayList();
Iterator iter = node.iterator("url-pattern");
while (iter.hasNext())
{
String p = iter.next().toString(false, true);
p = ServletPathSpec.normalize(p);
paths.add(p);
context.getMetaData().setOrigin(filterName + ".filter.mapping.url" + p, descriptor);
}
mapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
List names = new ArrayList();
iter = node.iterator("servlet-name");
while (iter.hasNext())
{
String n = ((XmlParser.Node)iter.next()).toString(false, true);
context.getMetaData().setOrigin(filterName + ".filter.mapping.servlet" + n, descriptor);
names.add(n);
}
mapping.setServletNames((String[])names.toArray(new String[names.size()]));
List dispatches = new ArrayList();
iter = node.iterator("dispatcher");
while (iter.hasNext())
{
String d = ((XmlParser.Node)iter.next()).toString(false, true);
dispatches.add(FilterMapping.dispatch(d));
}
if (dispatches.size() > 0)
mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
_filterMappings.add(mapping);
}
public void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//Additive across web.xml and web-fragment.xml
String uri = node.getString("taglib-uri", false, true);
String location = node.getString("taglib-location", false, true);
context.setResourceAlias(uri, location);
JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
if (config == null)
{
config = new JspConfig();
context.getServletContext().setJspConfigDescriptor(config);
}
TagLib tl = new TagLib();
tl.setTaglibLocation(location);
tl.setTaglibURI(uri);
config.addTaglibDescriptor(tl);
}
public void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//Additive across web.xml and web-fragment.xml
JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
if (config == null)
{
config = new JspConfig();
context.getServletContext().setJspConfigDescriptor(config);
}
for (int i = 0; i < node.size(); i++)
{
Object o = node.get(i);
if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node)o).getTag()))
visitTagLib(context, descriptor, (XmlParser.Node)o);
}
// Map URLs from jsp property groups to JSP servlet.
// this is more JSP stupidness creeping into the servlet spec
Iterator iter = node.iterator("jsp-property-group");
List paths = new ArrayList();
while (iter.hasNext())
{
JspPropertyGroup jpg = new JspPropertyGroup();
config.addJspPropertyGroup(jpg);
XmlParser.Node group = iter.next();
//url-patterns
Iterator iter2 = group.iterator("url-pattern");
while (iter2.hasNext())
{
String url = iter2.next().toString(false, true);
url = ServletPathSpec.normalize(url);
paths.add(url);
jpg.addUrlPattern(url);
}
jpg.setElIgnored(group.getString("el-ignored", false, true));
jpg.setPageEncoding(group.getString("page-encoding", false, true));
jpg.setScriptingInvalid(group.getString("scripting-invalid", false, true));
jpg.setIsXml(group.getString("is-xml", false, true));
jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true));
jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true));
jpg.setDefaultContentType(group.getString("default-content-type", false, true));
jpg.setBuffer(group.getString("buffer", false, true));
jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
//preludes
Iterator preludes = group.iterator("include-prelude");
while (preludes.hasNext())
{
String prelude = preludes.next().toString(false, true);
jpg.addIncludePrelude(prelude);
}
//codas
Iterator codas = group.iterator("include-coda");
while (codas.hasNext())
{
String coda = codas.next().toString(false, true);
jpg.addIncludeCoda(coda);
}
if (LOG.isDebugEnabled())
LOG.debug(config.toString());
}
//add mappings to the jsp servlet from the property-group mappings
if (paths.size() > 0)
{
ServletMapping jspMapping = null;
for (ServletMapping m : _servletMappings)
{
if (m.getServletName().equals("jsp"))
{
jspMapping = m;
break;
}
}
if (jspMapping != null)
{
if (jspMapping.getPathSpecs() == null)
{
//no paths in jsp servlet mapping, we will add all of ours
if (LOG.isDebugEnabled())
LOG.debug("Adding all paths from jsp-config to jsp servlet mapping");
jspMapping.setPathSpecs(paths.toArray(new String[paths.size()]));
}
else
{
//check if each of our paths is already present in existing mapping
ListIterator piterator = paths.listIterator();
while (piterator.hasNext())
{
String p = piterator.next();
if (jspMapping.containsPathSpec(p))
piterator.remove();
}
//any remaining paths, add to the jspMapping
if (paths.size() > 0)
{
for (String p : jspMapping.getPathSpecs())
{
paths.add(p);
}
if (LOG.isDebugEnabled())
LOG.debug("Adding extra paths from jsp-config to jsp servlet mapping");
jspMapping.setPathSpecs((String[])paths.toArray(new String[paths.size()]));
}
}
}
else
{
//no mapping for jsp yet, make one
ServletMapping mapping = new ServletMapping(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
mapping.setServletName("jsp");
mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
_servletMappings.add(mapping);
}
}
}
public void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
if (context.getSecurityHandler() == null)
{
LOG.warn("security-constraint declared but SecurityHandler==null");
return;
}
Constraint scBase = new Constraint();
//ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive
//across fragments
//TODO: need to remember origin of the constraints
try
{
XmlParser.Node auths = node.get("auth-constraint");
if (auths != null)
{
scBase.setAuthenticate(true);
// auth-constraint
Iterator iter = auths.iterator("role-name");
List roles = new ArrayList();
while (iter.hasNext())
{
String role = iter.next().toString(false, true);
roles.add(role);
}
scBase.setRoles(roles.toArray(new String[roles.size()]));
}
XmlParser.Node data = node.get("user-data-constraint");
if (data != null)
{
data = data.get("transport-guarantee");
String guarantee = data.toString(false, true).toUpperCase(Locale.ENGLISH);
if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee))
scBase.setDataConstraint(Constraint.DC_NONE);
else if ("INTEGRAL".equals(guarantee))
scBase.setDataConstraint(Constraint.DC_INTEGRAL);
else if ("CONFIDENTIAL".equals(guarantee))
scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
else
{
LOG.warn("Unknown user-data-constraint: {}", guarantee);
scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
}
}
Iterator iter = node.iterator("web-resource-collection");
while (iter.hasNext())
{
XmlParser.Node collection = iter.next();
String name = collection.getString("web-resource-name", false, true);
Constraint sc = (Constraint)scBase.clone();
sc.setName(name);
Iterator iter2 = collection.iterator("url-pattern");
while (iter2.hasNext())
{
String url = iter2.next().toString(false, true);
url = ServletPathSpec.normalize(url);
//remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
context.getMetaData().setOrigin("constraint.url." + url, descriptor);
Iterator methods = collection.iterator("http-method");
Iterator ommissions = collection.iterator("http-method-omission");
if (methods.hasNext())
{
if (ommissions.hasNext())
throw new IllegalStateException("web-resource-collection cannot contain both http-method and http-method-omission");
//configure all the http-method elements for each url
while (methods.hasNext())
{
String method = ((XmlParser.Node)methods.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethod(method);
mapping.setPathSpec(url);
mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
else if (ommissions.hasNext())
{
//configure all the http-method-omission elements for each url
// TODO use the array
while (ommissions.hasNext())
{
String method = ((XmlParser.Node)ommissions.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethodOmissions(new String[]{method});
mapping.setPathSpec(url);
mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
else
{
//No http-methods or http-method-omissions specified, the constraint applies to all
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec(url);
mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
}
}
catch (CloneNotSupportedException e)
{
LOG.warn("Unable to clone {}", scBase, e);
}
}
public void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
{
//ServletSpec 3.0 p74 says elements present 0/1 time if specified in web.xml take
//precendece over any web-fragment. If not specified in web.xml, then if specified
//in a web-fragment must be the same across all web-fragments.
XmlParser.Node method = node.get("auth-method");
if (method != null)
{
//handle auth-method merge
Origin origin = context.getMetaData().getOrigin("auth-method");
switch (origin)
{
case NotSet:
{
//not already set, so set it now
context.getSecurityHandler().setAuthMethod(method.toString(false, true));
context.getMetaData().setOrigin("auth-method", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSecurityHandler().setAuthMethod(method.toString(false, true));
context.getMetaData().setOrigin("auth-method", descriptor);
}
break;
}
case WebFragment:
{
//it was already set by another fragment, if we're parsing a fragment, the values must match
if (!context.getSecurityHandler().getAuthMethod().equals(method.toString(false, true)))
throw new IllegalStateException("Conflicting auth-method value in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
//handle realm-name merge
XmlParser.Node name = node.get("realm-name");
String nameStr = (name == null ? "default" : name.toString(false, true));
Origin originRealmName = context.getMetaData().getOrigin("realm-name");
switch (originRealmName)
{
case NotSet:
{
//no descriptor has set the realm-name yet, so set it
context.getSecurityHandler().setRealmName(nameStr);
context.getMetaData().setOrigin("realm-name", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//set by a web xml file (web.xml/web-default.xm/web-override.xml), only allow it to be changed by another web xml file
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSecurityHandler().setRealmName(nameStr);
context.getMetaData().setOrigin("realm-name", descriptor);
}
break;
}
case WebFragment:
{
//a fragment set it, and we must be parsing another fragment, so the values must match
if (!context.getSecurityHandler().getRealmName().equals(nameStr))
throw new IllegalStateException("Conflicting realm-name value in " + descriptor.getResource());
break;
}
default:
unknownOrigin(originRealmName);
}
if (Constraint.__FORM_AUTH.equalsIgnoreCase(context.getSecurityHandler().getAuthMethod()))
{
XmlParser.Node formConfig = node.get("form-login-config");
if (formConfig != null)
{
String loginPageName = null;
XmlParser.Node loginPage = formConfig.get("form-login-page");
if (loginPage != null)
loginPageName = loginPage.toString(false, true);
String errorPageName = null;
XmlParser.Node errorPage = formConfig.get("form-error-page");
if (errorPage != null)
errorPageName = errorPage.toString(false, true);
//handle form-login-page
Origin originFormLoginPage = context.getMetaData().getOrigin("form-login-page");
switch (originFormLoginPage)
{
case NotSet:
{
//Never been set before, so accept it
context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE, loginPageName);
context.getMetaData().setOrigin("form-login-page", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE, loginPageName);
context.getMetaData().setOrigin("form-login-page", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE).equals(loginPageName))
throw new IllegalStateException("Conflicting form-login-page value in " + descriptor.getResource());
break;
}
default:
unknownOrigin(originFormLoginPage);
}
//handle form-error-page
Origin originFormErrorPage = context.getMetaData().getOrigin("form-error-page");
switch (originFormErrorPage)
{
case NotSet:
{
//Never been set before, so accept it
context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE, errorPageName);
context.getMetaData().setOrigin("form-error-page", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
if (!(descriptor instanceof FragmentDescriptor))
{
context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE, errorPageName);
context.getMetaData().setOrigin("form-error-page", descriptor);
}
break;
}
case WebFragment:
{
//a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE).equals(errorPageName))
throw new IllegalStateException("Conflicting form-error-page value in " + descriptor.getResource());
break;
}
default:
unknownOrigin(originFormErrorPage);
}
}
else
{
throw new IllegalStateException("!form-login-config");
}
}
}
}
public void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
if (context.getSecurityHandler() == null)
{
LOG.warn("security-role declared but SecurityHandler==null");
return;
}
//ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged
XmlParser.Node roleNode = node.get("role-name");
String role = roleNode.toString(false, true);
((ConstraintAware)context.getSecurityHandler()).addRole(role);
context.getMetaData().setOrigin("security-role." + role, descriptor);
}
public void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String name = node.getString("filter-name", false, true);
FilterHolder holder = _filterHolderMap.get(name);
if (holder == null)
{
holder = context.getServletHandler().newFilterHolder(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
holder.setName(name);
_filterHolderMap.put(name, holder);
_filterHolders.add(holder);
}
String filterClass = node.getString("filter-class", false, true);
if (filterClass != null)
{
((WebDescriptor)descriptor).addClassName(filterClass);
Origin origin = context.getMetaData().getOrigin(name + ".filter.filter-class");
switch (origin)
{
case NotSet:
{
//no class set yet
holder.setClassName(filterClass);
context.getMetaData().setOrigin(name + ".filter.filter-class", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//filter class was set in web.xml, only allow other web xml descriptors (override/default) to change it
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setClassName(filterClass);
context.getMetaData().setOrigin(name + ".filter.filter-class", descriptor);
}
break;
}
case WebFragment:
{
//the filter class was set up by a web fragment, all fragments must be the same
if (!holder.getClassName().equals(filterClass))
throw new IllegalStateException("Conflicting filter-class for filter " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
Iterator iter = node.iterator("init-param");
while (iter.hasNext())
{
XmlParser.Node paramNode = iter.next();
String pname = paramNode.getString("param-name", false, true);
String pvalue = paramNode.getString("param-value", false, true);
Origin origin = context.getMetaData().getOrigin(name + ".filter.init-param." + pname);
switch (origin)
{
case NotSet:
{
//init-param not already set, so set it
holder.setInitParameter(pname, pvalue);
context.getMetaData().setOrigin(name + ".filter.init-param." + pname, descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
//otherwise just ignore it
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setInitParameter(pname, pvalue);
context.getMetaData().setOrigin(name + ".filter.init-param." + pname, descriptor);
}
break;
}
case WebFragment:
{
//previously set by a web-fragment, make sure that the value matches, otherwise its an error
if (!holder.getInitParameter(pname).equals(pvalue))
throw new IllegalStateException("Mismatching init-param " + pname + "=" + pvalue + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
String async = node.getString("async-supported", false, true);
if (async != null)
holder.setAsyncSupported(async.length() == 0 || Boolean.parseBoolean(async));
if (async != null)
{
boolean val = async.length() == 0 || Boolean.parseBoolean(async);
Origin origin = context.getMetaData().getOrigin(name + ".filter.async-supported");
switch (origin)
{
case NotSet:
{
//set it
holder.setAsyncSupported(val);
context.getMetaData().setOrigin(name + ".filter.async-supported", descriptor);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
{
//async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
if (!(descriptor instanceof FragmentDescriptor))
{
holder.setAsyncSupported(val);
context.getMetaData().setOrigin(name + ".filter.async-supported", descriptor);
}
break;
}
case WebFragment:
{
//async-supported set by another fragment, this fragment's value must match
if (holder.isAsyncSupported() != val)
throw new IllegalStateException("Conflicting async-supported=" + async + " for filter " + name + " in " + descriptor.getResource());
break;
}
default:
unknownOrigin(origin);
}
}
}
public void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//Servlet Spec 3.0, p74
//filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
//Maintenance update 3.0a to spec:
// Updated 8.2.3.g.v to say elements are additive across web-fragments.
String filterName = node.getString("filter-name", false, true);
Origin origin = context.getMetaData().getOrigin(filterName + ".filter.mappings");
switch (origin)
{
case NotSet:
{
//no filtermappings for this filter yet defined
context.getMetaData().setOrigin(filterName + ".filter.mappings", descriptor);
addFilterMapping(filterName, node, context, descriptor);
break;
}
case WebDefaults:
case WebOverride:
case WebXml:
{
//filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings.
if (!(descriptor instanceof FragmentDescriptor))
{
addFilterMapping(filterName, node, context, descriptor);
}
break;
}
case WebFragment:
{
//filter mappings first defined in a web-fragment, allow other fragments to add
addFilterMapping(filterName, node, context, descriptor);
break;
}
default:
unknownOrigin(origin);
}
}
public void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
String className = node.getString("listener-class", false, true);
try
{
if (className != null && className.length() > 0)
{
//Servlet Spec 3.0 p 74
//Duplicate listener declarations don't result in duplicate listener instances
for (ListenerHolder holder : context.getServletHandler().getListeners())
{
if (holder.getClassName().equals(className))
return;
}
((WebDescriptor)descriptor).addClassName(className);
ListenerHolder h = context.getServletHandler().newListenerHolder(new Source(Source.Origin.DESCRIPTOR, descriptor.getResource().toString()));
h.setClassName(className);
context.getServletHandler().addListener(h);
context.getMetaData().setOrigin(className + ".listener", descriptor);
}
}
catch (Exception e)
{
LOG.warn("Could not instantiate listener {}", className, e);
return;
}
}
/**
* Servlet spec 3.1. When present in web.xml, this means that http methods that are
* not covered by security constraints should have access denied.
*
* See section 13.8.4, pg 145
*
* @param context the of the processing
* @param descriptor the descriptor
* @param node the xml node
*/
public void visitDenyUncoveredHttpMethods(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
if (context.getSecurityHandler() == null)
{
LOG.warn("deny-uncovered-http-methods declared but SecurityHandler==null");
return;
}
((ConstraintAware)context.getSecurityHandler()).setDenyUncoveredHttpMethods(true);
}
/**
* When specified, this element provides a default context path
* of the web application. The default context path starts
* with a / character. If it is not rooted at the root of the
* server's name space, the path does not end with a / character.
*
* @param context the of the processing
* @param descriptor the descriptor
* @param node the xml node
* @since Servlet 4.0
*/
public void visitDefaultContextPath(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
if (!(descriptor instanceof FragmentDescriptor))
{
String path = node.toString(false, true);
context.setAttribute("default-context-path", path);
if (context.isContextPathDefault())
{
context.setDefaultContextPath(path);
context.getMetaData().setOrigin("default-context-path", descriptor);
}
}
}
/**
* When specified, this element provides a default request
* encoding of the web application.
*
* @param context the of the processing
* @param descriptor the descriptor
* @param node the xml node
* @since Servlet 4.0
*/
public void visitRequestCharacterEncoding(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//As per spec, this element can only appear in web.xml, never in a fragment. Jetty will
//allow it to be specified in webdefault.xml, web.xml, and web-override.xml.
if (!(descriptor instanceof FragmentDescriptor))
{
String encoding = node.toString(false, true);
context.setAttribute("request-character-encoding", encoding);
context.setDefaultRequestCharacterEncoding(encoding);
context.getMetaData().setOrigin("request-character-encoding", descriptor);
}
}
/**
* When specified, this element provides a default response
* encoding of the web application.
*
* @param context the of the processing
* @param descriptor the descriptor
* @param node the xml node
* @since Servlet 4.0
*/
public void visitResponseCharacterEncoding(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
{
//As per spec, this element can only appear in web.xml, never in a fragment. Jetty will
//allow it to be specified in webdefault.xml, web.xml, and web-override.xml.
if (!(descriptor instanceof FragmentDescriptor))
{
String encoding = node.toString(false, true);
context.setAttribute("response-character-encoding", encoding);
context.setDefaultResponseCharacterEncoding(encoding);
context.getMetaData().setOrigin("response-character-encoding", descriptor);
}
}
private void unknownOrigin(Origin origin)
{
LOG.warn("Unknown descriptor origin {}", origin, new Throwable()); // TODO throw ISE?
}
}