Index: 3rdParty_sources/undertow/io/undertow/servlet/ServletExtension.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/ServletExtension.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/ServletExtension.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,45 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet; + +import io.undertow.servlet.api.DeploymentInfo; + +import javax.servlet.ServletContext; + +/** + * + * Interface that allows the servlet deployment to be modified before it is deployed. + * + * These extensions are loaded using a {@link java.util.ServiceLoader} from the deployment + * class loader, and are the first things run after the servlet context is created. + * + * There are many possible use cases for these extensions. Some obvious ones are: + * + * - Adding additional handlers + * - Adding new authentication mechanisms + * - Adding and removing servlets + * + * + * @author Stuart Douglas + */ +public interface ServletExtension { + + void handleDeployment(final DeploymentInfo deploymentInfo, final ServletContext servletContext); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/Servlets.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/Servlets.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/Servlets.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,158 @@ +package io.undertow.servlet; + +import javax.servlet.Filter; +import javax.servlet.MultipartConfigElement; +import javax.servlet.Servlet; + +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.WebResourceCollection; +import io.undertow.servlet.core.ServletContainerImpl; + +import java.util.EventListener; + +/** + * Utility class for building servlet deployments. + * + * @author Stuart Douglas + */ +public class Servlets { + + private static volatile ServletContainer container; + + /** + * Returns the default servlet container. For most embedded use + * cases this will be sufficient. + * + * @return The default servlet container + */ + public static ServletContainer defaultContainer() { + if (container != null) { + return container; + } + synchronized (Servlets.class) { + if (container != null) { + return container; + } + return container = ServletContainer.Factory.newInstance(); + } + } + + /** + * Creates a new servlet container. + * + * @return A new servlet container + */ + public static ServletContainer newContainer() { + return new ServletContainerImpl(); + } + + /** + * Creates a new servlet deployment info structure + * + * @return A new deployment info structure + */ + public static DeploymentInfo deployment() { + return new DeploymentInfo(); + } + + /** + * Creates a new servlet description with the given name and class + * + * @param name The servlet name + * @param servletClass The servlet class + * @return A new servlet description + */ + public static ServletInfo servlet(final String name, final Class servletClass) { + return new ServletInfo(name, servletClass); + } + + /** + * Creates a new servlet description with the given name and class + * + * @param name The servlet name + * @param servletClass The servlet class + * @return A new servlet description + */ + public static ServletInfo servlet(final String name, final Class servletClass, final InstanceFactory servlet) { + return new ServletInfo(name, servletClass, servlet); + } + + + /** + * Creates a new servlet description with the given name and class + * + * @param name The filter name + * @param filterClass The filter class + * @return A new servlet description + */ + public static FilterInfo filter(final String name, final Class filterClass) { + return new FilterInfo(name, filterClass); + } + + /** + * Creates a new servlet description with the given name and class + * + * @param name The filter name + * @param filterClass The filter class + * @return A new filter description + */ + public static FilterInfo filter(final String name, final Class filterClass, final InstanceFactory filter) { + return new FilterInfo(name, filterClass, filter); + } + + /** + * Creates a new multipart config element + * + * @param location the directory location where files will be stored + * @param maxFileSize the maximum size allowed for uploaded files + * @param maxRequestSize the maximum size allowed for + * multipart/form-data requests + * @param fileSizeThreshold the size threshold after which files will + * be written to disk + */ + public static MultipartConfigElement multipartConfig(String location, long maxFileSize, long maxRequestSize, int fileSizeThreshold) { + return new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold); + } + + public static ListenerInfo listener(final Class listenerClass, final InstanceFactory instanceFactory) { + return new ListenerInfo(listenerClass, instanceFactory); + } + + public static ListenerInfo listener(final Class listenerClass) { + return new ListenerInfo(listenerClass); + } + + public static SecurityConstraint securityConstraint() { + return new SecurityConstraint(); + } + + public static WebResourceCollection webResourceCollection() { + return new WebResourceCollection(); + } + + private Servlets() { + } + + public static LoginConfig loginConfig(String realmName, String loginPage, String errorPage) { + return new LoginConfig(realmName, loginPage, errorPage); + } + + public static LoginConfig loginConfig(final String realmName) { + return new LoginConfig(realmName); + } + + public static LoginConfig loginConfig(String mechanismName, String realmName, String loginPage, String errorPage) { + return new LoginConfig(mechanismName, realmName, loginPage, errorPage); + } + + public static LoginConfig loginConfig(String mechanismName, final String realmName) { + return new LoginConfig(mechanismName, realmName); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletLogger.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletLogger.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletLogger.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,99 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Date; +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.Cause; +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; + +/** + * log messages start at 15000 + * + * @author Stuart Douglas + */ +@MessageLogger(projectCode = "UT") +public interface UndertowServletLogger extends BasicLogger { + + UndertowServletLogger ROOT_LOGGER = Logger.getMessageLogger(UndertowServletLogger.class, UndertowServletLogger.class.getPackage().getName()); + + UndertowServletLogger REQUEST_LOGGER = Logger.getMessageLogger(UndertowServletLogger.class, UndertowServletLogger.class.getPackage().getName() + ".request"); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15000, value = "IOException handling request") + void ioExceptionHandingRequest(@Cause IOException e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15001, value = "ServletException handling request") + void servletExceptionHandlingRequest(@Cause ServletException e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15002, value = "Stopping servlet %s due to permanent unavailability") + void stoppingServletDueToPermanentUnavailability(final String servlet, @Cause UnavailableException e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15003, value = "Stopping servlet %s till %s due to temporary unavailability") + void stoppingServletUntilDueToTemporaryUnavailability(String name, Date till, @Cause UnavailableException e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15004, value = "Malformed URL exception reading resource %s") + void malformedUrlException(String relativePath, @Cause MalformedURLException e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15005, value = "Error invoking method %s on listener %s") + void errorInvokingListener(final String method, Class listenerClass, @Cause Exception e); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15006, value = "IOException dispatching async event") + void ioExceptionDispatchingAsyncEvent(@Cause IOException e); + + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 15007, value = "Stack trace on error enabled for deployment %s, please do not enable for production use") + void servletStackTracesAll(String deploymentName); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 15008, value = "Failed to load development mode persistent sessions") + void failedtoLoadPersistentSessions(@Cause Exception e); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 15009, value = "Failed to persist session attribute %s with value %s for session %s") + void failedToPersistSessionAttribute(String attributeName, Object value, String sessionID, @Cause Exception e); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 15010, value = "Failed to persist sessions") + void failedToPersistSessions(@Cause Exception e); + + @LogMessage(level = Logger.Level.WARN) + @Message(id = 15011, value = "Non standard filter mapping '*' for filter %s. Portable application should use '/*' instead.") + void nonStandardFilterMapping(String filterName); + + @LogMessage(level = Logger.Level.ERROR) + @Message(id = 15012, value = "Failed to generate error page %s for original exception: %s. Generating error page resulted in a %s.") + void errorGeneratingErrorPage(String originalErrorPage, Object originalException, int code, @Cause Throwable cause); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletMessages.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletMessages.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/UndertowServletMessages.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,202 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; + +import javax.servlet.Filter; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import io.undertow.servlet.api.DeploymentManager; +import org.jboss.logging.annotations.Cause; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageBundle; +import org.jboss.logging.Messages; + +/** + * messages start at 10000 + * + * @author Stuart Douglas + */ +@MessageBundle(projectCode = "UT") +public interface UndertowServletMessages { + + UndertowServletMessages MESSAGES = Messages.getBundle(UndertowServletMessages.class); + + @Message(id = 10000, value = "%s cannot be null") + IllegalArgumentException paramCannotBeNull(String param); + + @Message(id = 10001, value = "%s cannot be null for %s named %s") + IllegalArgumentException paramCannotBeNull(String param, String componentType, String name); + + @Message(id = 10002, value = "Deployments can only be removed when in undeployed state, but state was %s") + IllegalStateException canOnlyRemoveDeploymentsWhenUndeployed(DeploymentManager.State state); + + @Message(id = 10003, value = "Cannot call getInputStream(), getReader() already called") + IllegalStateException getReaderAlreadyCalled(); + + @Message(id = 10004, value = "Cannot call getReader(), getInputStream() already called") + IllegalStateException getInputStreamAlreadyCalled(); + + @Message(id = 10005, value = "Cannot call getOutputStream(), getWriter() already called") + IllegalStateException getWriterAlreadyCalled(); + + @Message(id = 10006, value = "Cannot call getWriter(), getOutputStream() already called") + IllegalStateException getOutputStreamAlreadyCalled(); + + @Message(id = 10007, value = "Two servlets specified with same mapping %s") + IllegalArgumentException twoServletsWithSameMapping(String path); + + @Message(id = 10008, value = "Header %s cannot be converted to a date") + IllegalArgumentException headerCannotBeConvertedToDate(String header); + + @Message(id = 10009, value = "Servlet %s of type %s does not implement javax.servlet.Servlet") + IllegalArgumentException servletMustImplementServlet(String name, Class servletClass); + + @Message(id = 10010, value = "%s of type %s must have a default constructor") + IllegalArgumentException componentMustHaveDefaultConstructor(String componentType, Class componentClass); + + @Message(id = 10011, value = "Filter %s of type %s does not implement javax.servlet.Filter") + IllegalArgumentException filterMustImplementFilter(String name, Class filterClass); + + @Message(id = 10012, value = "Listener class %s must implement at least one listener interface") + IllegalArgumentException listenerMustImplementListenerClass(Class listenerClass); + + @Message(id = 10013, value = "Could not instantiate %s") + ServletException couldNotInstantiateComponent(String name, @Cause Exception e); + + @Message(id = 10014, value = "Could not load class %s") + RuntimeException cannotLoadClass(String className, @Cause Exception e); + + @Message(id = 10015, value = "Could not delete file %s") + IOException deleteFailed(File file); + + @Message(id = 10016, value = "Not a multi part request") + ServletException notAMultiPartRequest(); + + @Message(id = 10017, value = "Request was neither the original request object or a ServletRequestWrapper") + IllegalArgumentException requestNoOfCorrectType(); + + @Message(id = 10018, value = "Async not started") + IllegalStateException asyncNotStarted(); + + @Message(id = 10019, value = "Response already commited") + IllegalStateException responseAlreadyCommited(); + + @Message(id = 10020, value = "Content has been written") + IllegalStateException contentHasBeenWritten(); + + @Message(id = 10021, value = "Path %s must start with a /") + MalformedURLException pathMustStartWithSlash(String path); + + @Message(id = 10022, value = "Session is invalid") + IllegalStateException sessionIsInvalid(); + + @Message(id = 10023, value = "Request %s was not original or a wrapper") + IllegalArgumentException requestWasNotOriginalOrWrapper(ServletRequest request); + + @Message(id = 10024, value = "Response %s was not original or a wrapper") + IllegalArgumentException responseWasNotOriginalOrWrapper(ServletResponse response); + + @Message(id = 10025, value = "Async request already dispatched") + IllegalStateException asyncRequestAlreadyDispatched(); + + @Message(id = 10026, value = "Async is not supported for this request, as not all filters or Servlets were marked as supporting async") + IllegalStateException startAsyncNotAllowed(); + + @Message(id = 10027, value = "Not implemented") + IllegalStateException notImplemented(); + + @Message(id = 10028, value = "Async processing already started") + IllegalStateException asyncAlreadyStarted(); + + @Message(id = 10029, value = "Stream is closed") + IOException streamIsClosed(); + + @Message(id = 10030, value = "User already logged in") + ServletException userAlreadyLoggedIn(); + + @Message(id = 10031, value = "Login failed") + ServletException loginFailed(); + + @Message(id = 10032, value = "Authenticationfailed") + ServletException authenticationFailed(); + + @Message(id = 10033, value = "No session") + IllegalStateException noSession(); + + @Message(id = 10034, value = "Stream not in async mode") + IllegalStateException streamNotInAsyncMode(); + + @Message(id = 10035, value = "Stream in async mode was not ready for IO operation") + IllegalStateException streamNotReady(); + + @Message(id = 10036, value = "Listener has already been set") + IllegalStateException listenerAlreadySet(); + + @Message(id = 10037, value = "When stream is in async mode a write can only be made from the listener callback") + IllegalStateException writeCanOnlyBeMadeFromListenerCallback(); + + @Message(id = 10038, value = "No web socket handler was provided to the web socket servlet") + ServletException noWebSocketHandler(); + + @Message(id = 10039, value = "Unknown authentication mechanism %s") + RuntimeException unknownAuthenticationMechanism(String mechName); + + @Message(id = 10040, value = "More than one default error page %s and %s") + IllegalStateException moreThanOneDefaultErrorPage(String defaultErrorPage, String location); + + @Message(id = 10041, value = "The servlet context has already been initialized, you can only call this method from a ServletContainerInitializer or a ServletContextListener") + IllegalStateException servletContextAlreadyInitialized(); + + @Message(id = 10042, value = "This method cannot be called from a servlet context listener that has been added programatically") + UnsupportedOperationException cannotCallFromProgramaticListener(); + + @Message(id = 10043, value = "Cannot add servlet context listener from a programatically added listener") + IllegalArgumentException cannotAddServletContextListener(); + + @Message(id = 10044, value = "listener cannot be null") + NullPointerException listenerCannotBeNull(); + + @Message(id = 10045, value = "SSL cannot be combined with any other method") + IllegalArgumentException sslCannotBeCombinedWithAnyOtherMethod(); + + @Message(id = 10046, value = "No servlet context at %s to dispatch to") + IllegalArgumentException couldNotFindContextToDispatchTo(String originalContextPath); + + @Message(id = 10047, value = "Name was null") + NullPointerException nullName(); + + @Message(id = 10048, value = "Can only handle HTTP type of request / response: %s / %s") + IllegalArgumentException invalidRequestResponseType(ServletRequest request, ServletResponse response); + + @Message(id = 10049, value = "Async request already returned to container") + IllegalStateException asyncRequestAlreadyReturnedToContainer(); + + @Message(id = 10050, value = "Filter %s used in filter mapping %s not found") + IllegalStateException filterNotFound(String filterName, String mapping); + + @Message(id = 10051, value = "Deployment %s has stopped") + ServletException deploymentStopped(String deployment); +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/AuthMethodConfig.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/AuthMethodConfig.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/AuthMethodConfig.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,36 @@ +package io.undertow.servlet.api; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Stuart Douglas + */ +public class AuthMethodConfig implements Cloneable { + + private final String name; + private final Map properties; + + public AuthMethodConfig(String name, Map properties) { + this.name = name; + this.properties = new HashMap(properties); + } + + public AuthMethodConfig(String name) { + this.name = name; + this.properties = new HashMap(); + } + + public String getName() { + return name; + } + + public Map getProperties() { + return properties; + } + + @Override + public AuthMethodConfig clone() { + return new AuthMethodConfig(name, properties); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/AuthorizationManager.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/AuthorizationManager.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/AuthorizationManager.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,48 @@ +package io.undertow.servlet.api; + +import io.undertow.security.idm.Account; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * Authorization manager. The servlet implementation delegates all authorization checks to this interface. + * + * @author Stuart Douglas + */ +public interface AuthorizationManager { + + /** + * Tests if a user is in a given role + * @param roleName The role name + * @param account The user account + * @param servletInfo The servlet info for the target servlet + * @param request The servlet request + * @param deployment The deployment + * @return true if the user is in the role + */ + boolean isUserInRole(String roleName, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, Deployment deployment); + + /** + * Tests if a user can access a given resource + * + * @param mappedConstraints The constraints + * @param account The users account + * @param servletInfo The servlet info for the target servlet + * @param request The servlet request + * @param deployment The deployment + * @return true if the user can access the resource + */ + boolean canAccessResource(List mappedConstraints, final Account account, final ServletInfo servletInfo, final HttpServletRequest request, Deployment deployment); + + /** + * Determines the transport guarantee type + * + * @param currentConnectionGuarantee The current connections transport guarantee type + * @param configuredRequiredGuarantee The transport guarantee type specified in the deployment descriptor/annotations + * @param request The request + * @return The transport guarantee type + */ + TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentConnectionGuarantee, TransportGuaranteeType configuredRequiredGuarantee, final HttpServletRequest request); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ClassIntrospecter.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ClassIntrospecter.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ClassIntrospecter.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,35 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +/** + * + * Interface that is provided by the container to create a servlet / filter / listener + * definition from a given class, based on the annotations present on the class. + * + * This is needed to allow for annotations to be taken into account when servlets etc are + * added programatically. + * + * @author Stuart Douglas + */ +public interface ClassIntrospecter { + + InstanceFactory createInstanceFactory(final Class clazz) throws NoSuchMethodException; + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ConfidentialPortManager.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ConfidentialPortManager.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ConfidentialPortManager.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,31 @@ +/* + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import io.undertow.server.HttpServerExchange; + +/** + * A utility to take the {@see HttpServerExchange} of the current request and obtain the number of the port number to use in + * https redirects. + * + * @author Darran Lofthouse + */ +public interface ConfidentialPortManager { + + int getConfidentialPort(final HttpServerExchange exchange); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/DefaultServletConfig.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/DefaultServletConfig.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/DefaultServletConfig.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,60 @@ +package io.undertow.servlet.api; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * The default servlet config. By default this has quite a restrictive configuration, only allowing + * extensions in common use in the web to be served. + * + * This class is deprecated, the default servlet should be configured via context params. + * + * @author Stuart Douglas + */ +@Deprecated +public class DefaultServletConfig { + + private static final String[] DEFAULT_ALLOWED_EXTENSIONS = {"js", "css", "png", "jpg", "gif", "html", "htm", "txt", "pdf"}; + private static final String[] DEFAULT_DISALLOWED_EXTENSIONS = {"class", "jar", "war", "zip", "xml"}; + + private final boolean defaultAllowed; + private final Set allowed; + private final Set disallowed; + + public DefaultServletConfig(final boolean defaultAllowed, final Set exceptions) { + this.defaultAllowed = defaultAllowed; + if(defaultAllowed) { + disallowed = Collections.unmodifiableSet(new HashSet(exceptions)); + allowed = null; + } else { + allowed = Collections.unmodifiableSet(new HashSet(exceptions)); + disallowed = null; + } + } + + public DefaultServletConfig(final boolean defaultAllowed) { + this.defaultAllowed = defaultAllowed; + this.allowed = Collections.unmodifiableSet(new HashSet(Arrays.asList(DEFAULT_ALLOWED_EXTENSIONS))); + this.disallowed = Collections.unmodifiableSet(new HashSet(Arrays.asList(DEFAULT_DISALLOWED_EXTENSIONS))); + } + + public DefaultServletConfig() { + this.defaultAllowed = false; + this.allowed = Collections.unmodifiableSet(new HashSet(Arrays.asList(DEFAULT_ALLOWED_EXTENSIONS))); + this.disallowed = Collections.unmodifiableSet(new HashSet(Arrays.asList(DEFAULT_DISALLOWED_EXTENSIONS))); + } + + public boolean isDefaultAllowed() { + return defaultAllowed; + } + + public Set getAllowed() { + return allowed; + } + + public Set getDisallowed() { + return disallowed; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/Deployment.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/Deployment.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/Deployment.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,96 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.server.HttpHandler; +import io.undertow.server.session.SessionManager; +import io.undertow.servlet.core.ManagedFilters; +import io.undertow.servlet.core.ApplicationListeners; +import io.undertow.servlet.core.ManagedServlets; +import io.undertow.servlet.core.CompositeThreadSetupAction; +import io.undertow.servlet.core.ErrorPages; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.servlet.spec.ServletContextImpl; + +/** + * Runtime representation of a deployment. + * + * @author Stuart Douglas + */ +public interface Deployment { + + DeploymentInfo getDeploymentInfo(); + + ServletContainer getServletContainer(); + + ApplicationListeners getApplicationListeners(); + + ManagedServlets getServlets(); + + ManagedFilters getFilters(); + + ServletContextImpl getServletContext(); + + HttpHandler getHandler(); + + ServletPathMatches getServletPaths(); + + CompositeThreadSetupAction getThreadSetupAction(); + + ErrorPages getErrorPages(); + + Map getMimeExtensionMappings(); + + ServletDispatcher getServletDispatcher(); + + /** + * + * @return The session manager + */ + SessionManager getSessionManager(); + + /** + * + * @return The executor used for servlet requests. May be null in which case the XNIO worker is used + */ + Executor getExecutor(); + + /** + * + * @return The executor used for async request dispatches. May be null in which case the XNIO worker is used + */ + Executor getAsyncExecutor(); + + Charset getDefaultCharset(); + + /** + * + * @return The list of authentication mechanisms configured for this deployment + */ + List getAuthenticationMechanisms(); + + DeploymentManager.State getDeploymentState(); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,1082 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; + +import javax.servlet.DispatcherType; +import javax.servlet.descriptor.JspConfigDescriptor; + +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.security.api.AuthenticationMechanismFactory; +import io.undertow.security.api.NotificationReceiver; +import io.undertow.security.api.SecurityContextFactory; +import io.undertow.security.idm.IdentityManager; +import io.undertow.server.HandlerWrapper; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.servlet.ServletExtension; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.core.DefaultAuthorizationManager; +import io.undertow.servlet.core.InMemorySessionManagerFactory; +import io.undertow.servlet.util.DefaultClassIntrospector; +import io.undertow.util.ImmediateAuthenticationMechanismFactory; + +/** + * Represents a servlet deployment. + * + * @author Stuart Douglas + */ +public class DeploymentInfo implements Cloneable { + + private String deploymentName; + private String displayName; + private String contextPath; + private ClassLoader classLoader; + private ResourceManager resourceManager = ResourceManager.EMPTY_RESOURCE_MANAGER; + private ClassIntrospecter classIntrospecter = DefaultClassIntrospector.INSTANCE; + private int majorVersion = 3; + private int minorVersion; + private Executor executor; + private Executor asyncExecutor; + private File tempDir; + private JspConfigDescriptor jspConfigDescriptor; + private DefaultServletConfig defaultServletConfig; + private SessionManagerFactory sessionManagerFactory = new InMemorySessionManagerFactory(); + private LoginConfig loginConfig; + private IdentityManager identityManager; + private ConfidentialPortManager confidentialPortManager; + private boolean allowNonStandardWrappers = false; + private int defaultSessionTimeout = 60 * 30; + private ConcurrentMap servletContextAttributeBackingMap; + private ServletSessionConfig servletSessionConfig; + private String hostName = "localhost"; + private boolean denyUncoveredHttpMethods = false; + private ServletStackTraces servletStackTraces = ServletStackTraces.LOCAL_ONLY; + private boolean invalidateSessionOnLogout = false; + private int defaultCookieVersion = 0; + private SessionPersistenceManager sessionPersistenceManager; + private String defaultEncoding = "ISO-8859-1"; + private String urlEncoding = null; + private boolean ignoreFlush = false; + private AuthorizationManager authorizationManager = DefaultAuthorizationManager.INSTANCE; + private AuthenticationMechanism jaspiAuthenticationMechanism; + private SecurityContextFactory securityContextFactory; + private String serverName = "Undertow"; + private MetricsCollector metricsCollector = null; + private SessionConfigWrapper sessionConfigWrapper = null; + private boolean eagerFilterInit = false; + private boolean disableCachingForSecuredPages = true; + private final Map servlets = new HashMap(); + private final Map filters = new HashMap(); + private final List filterServletNameMappings = new ArrayList(); + private final List filterUrlMappings = new ArrayList(); + private final List listeners = new ArrayList(); + private final List servletContainerInitializers = new ArrayList(); + private final List threadSetupActions = new ArrayList(); + private final Map initParameters = new HashMap(); + private final Map servletContextAttributes = new HashMap(); + private final Map localeCharsetMapping = new HashMap(); + private final List welcomePages = new ArrayList(); + private final List errorPages = new ArrayList(); + private final List mimeMappings = new ArrayList(); + private final List securityConstraints = new ArrayList(); + private final Set securityRoles = new HashSet(); + private final List notificationReceivers = new ArrayList(); + private final Map authenticationMechanisms = new HashMap(); + + /** + * additional servlet extensions + */ + private final List servletExtensions = new ArrayList(); + + /** + * map of additional roles that should be applied to the given principal. + */ + private final Map> principalVersusRolesMap = new HashMap>(); + + /** + * Wrappers that are applied before the servlet initial handler, and before any servlet related object have been + * created. If a wrapper wants to bypass servlet entirely it should register itself here. + */ + private final List initialHandlerChainWrappers = new ArrayList(); + + /** + * Handler chain wrappers that are applied outside all other handlers, including security but after the initial + * servlet handler. + */ + private final List outerHandlerChainWrappers = new ArrayList(); + + /** + * Handler chain wrappers that are applied just before the servlet request is dispatched. At this point the security + * handlers have run, and any security information is attached to the request. + */ + private final List innerHandlerChainWrappers = new ArrayList(); + + + public void validate() { + if (deploymentName == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("deploymentName"); + } + if (contextPath == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("contextName"); + } + if (classLoader == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classLoader"); + } + if (resourceManager == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("resourceManager"); + } + if (classIntrospecter == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("classIntrospecter"); + } + if (defaultEncoding == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("defaultEncoding"); + } + + for (final ServletInfo servlet : this.servlets.values()) { + servlet.validate(); + } + for (final FilterInfo filter : this.filters.values()) { + filter.validate(); + } + for (FilterMappingInfo mapping : this.filterServletNameMappings) { + if (!this.filters.containsKey(mapping.getFilterName())) { + throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping()); + } + } + for (FilterMappingInfo mapping : this.filterUrlMappings) { + if (!this.filters.containsKey(mapping.getFilterName())) { + throw UndertowServletMessages.MESSAGES.filterNotFound(mapping.getFilterName(), mapping.getMappingType() + " - " + mapping.getMapping()); + } + } + } + + public String getDeploymentName() { + return deploymentName; + } + + public DeploymentInfo setDeploymentName(final String deploymentName) { + this.deploymentName = deploymentName; + return this; + } + + public String getDisplayName() { + return displayName; + } + + public DeploymentInfo setDisplayName(final String displayName) { + this.displayName = displayName; + return this; + } + + public String getContextPath() { + return contextPath; + } + + public DeploymentInfo setContextPath(final String contextPath) { + this.contextPath = contextPath; + return this; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public DeploymentInfo setClassLoader(final ClassLoader classLoader) { + this.classLoader = classLoader; + return this; + } + + public ResourceManager getResourceManager() { + return resourceManager; + } + + public DeploymentInfo setResourceManager(final ResourceManager resourceManager) { + this.resourceManager = resourceManager; + return this; + } + + public ClassIntrospecter getClassIntrospecter() { + return classIntrospecter; + } + + public DeploymentInfo setClassIntrospecter(final ClassIntrospecter classIntrospecter) { + this.classIntrospecter = classIntrospecter; + return this; + } + + public boolean isAllowNonStandardWrappers() { + return allowNonStandardWrappers; + } + + public DeploymentInfo setAllowNonStandardWrappers(final boolean allowNonStandardWrappers) { + this.allowNonStandardWrappers = allowNonStandardWrappers; + return this; + } + + public int getDefaultSessionTimeout() { + return defaultSessionTimeout; + } + + /** + * @param defaultSessionTimeout The default session timeout, in seconds + */ + public DeploymentInfo setDefaultSessionTimeout(final int defaultSessionTimeout) { + this.defaultSessionTimeout = defaultSessionTimeout; + return this; + } + + public String getDefaultEncoding() { + return defaultEncoding; + } + + /** + * Sets the default encoding that will be used for servlet responses + * + * @param defaultEncoding The default encoding + */ + public DeploymentInfo setDefaultEncoding(String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + return this; + } + + public String getUrlEncoding() { + return urlEncoding; + } + + /** + * Sets the URL encoding. This will only take effect if the {@link io.undertow.UndertowOptions#DECODE_URL} + * parameter has been set to false. This allows multiple deployments in the same server to use a different URL encoding + * + * @param urlEncoding The encoding to use + */ + public DeploymentInfo setUrlEncoding(String urlEncoding) { + this.urlEncoding = urlEncoding; + return this; + } + + public DeploymentInfo addServlet(final ServletInfo servlet) { + servlets.put(servlet.getName(), servlet); + return this; + } + + public DeploymentInfo addServlets(final ServletInfo... servlets) { + for (final ServletInfo servlet : servlets) { + addServlet(servlet); + } + return this; + } + + public DeploymentInfo addServlets(final Collection servlets) { + for (final ServletInfo servlet : servlets) { + addServlet(servlet); + } + return this; + } + + public Map getServlets() { + return Collections.unmodifiableMap(servlets); + } + + + public DeploymentInfo addFilter(final FilterInfo filter) { + filters.put(filter.getName(), filter); + return this; + } + + public DeploymentInfo addFilters(final FilterInfo... filters) { + for (final FilterInfo filter : filters) { + addFilter(filter); + } + return this; + } + + public DeploymentInfo addFilters(final Collection filters) { + for (final FilterInfo filter : filters) { + addFilter(filter); + } + return this; + } + + public Map getFilters() { + return Collections.unmodifiableMap(filters); + } + + public DeploymentInfo addFilterUrlMapping(final String filterName, final String mapping, DispatcherType dispatcher) { + filterUrlMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher)); + return this; + } + + public DeploymentInfo addFilterServletNameMapping(final String filterName, final String mapping, DispatcherType dispatcher) { + filterServletNameMappings.add(new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher)); + return this; + } + + public DeploymentInfo insertFilterUrlMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) { + filterUrlMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.URL, mapping, dispatcher)); + return this; + } + + public DeploymentInfo insertFilterServletNameMapping(final int pos, final String filterName, final String mapping, DispatcherType dispatcher) { + filterServletNameMappings.add(pos, new FilterMappingInfo(filterName, FilterMappingInfo.MappingType.SERVLET, mapping, dispatcher)); + return this; + } + + public List getFilterMappings() { + final ArrayList ret = new ArrayList(filterUrlMappings); + ret.addAll(filterServletNameMappings); + return Collections.unmodifiableList(ret); + } + + + public DeploymentInfo addListener(final ListenerInfo listener) { + listeners.add(listener); + return this; + } + + public DeploymentInfo addListeners(final ListenerInfo... listeners) { + this.listeners.addAll(Arrays.asList(listeners)); + return this; + } + + public DeploymentInfo addListeners(final Collection listeners) { + this.listeners.addAll(listeners); + return this; + } + + public List getListeners() { + return listeners; + } + + public int getMajorVersion() { + return majorVersion; + } + + public DeploymentInfo setMajorVersion(final int majorVersion) { + this.majorVersion = majorVersion; + return this; + } + + public int getMinorVersion() { + return minorVersion; + } + + public DeploymentInfo setMinorVersion(final int minorVersion) { + this.minorVersion = minorVersion; + return this; + } + + public DeploymentInfo addServletContainerInitalizer(final ServletContainerInitializerInfo servletContainerInitializer) { + servletContainerInitializers.add(servletContainerInitializer); + return this; + } + + public DeploymentInfo addServletContainerInitalizers(final ServletContainerInitializerInfo... servletContainerInitializer) { + servletContainerInitializers.addAll(Arrays.asList(servletContainerInitializer)); + return this; + } + + public DeploymentInfo addServletContainerInitalizers(final List servletContainerInitializer) { + servletContainerInitializers.addAll(servletContainerInitializer); + return this; + } + + public List getServletContainerInitializers() { + return servletContainerInitializers; + } + + public DeploymentInfo addThreadSetupAction(final ThreadSetupAction action) { + threadSetupActions.add(action); + return this; + } + + public List getThreadSetupActions() { + return threadSetupActions; + } + + public boolean isEagerFilterInit() { + return eagerFilterInit; + } + + public DeploymentInfo setEagerFilterInit(boolean eagerFilterInit) { + this.eagerFilterInit = eagerFilterInit; + return this; + } + + public DeploymentInfo addInitParameter(final String name, final String value) { + initParameters.put(name, value); + return this; + } + + public Map getInitParameters() { + return Collections.unmodifiableMap(initParameters); + } + + public DeploymentInfo addServletContextAttribute(final String name, final Object value) { + servletContextAttributes.put(name, value); + return this; + } + + public Map getServletContextAttributes() { + return Collections.unmodifiableMap(servletContextAttributes); + } + + public DeploymentInfo addWelcomePage(final String welcomePage) { + this.welcomePages.add(welcomePage); + return this; + } + + public DeploymentInfo addWelcomePages(final String... welcomePages) { + this.welcomePages.addAll(Arrays.asList(welcomePages)); + return this; + } + + public DeploymentInfo addWelcomePages(final Collection welcomePages) { + this.welcomePages.addAll(welcomePages); + return this; + } + + public List getWelcomePages() { + return Collections.unmodifiableList(welcomePages); + } + + public DeploymentInfo addErrorPage(final ErrorPage errorPage) { + this.errorPages.add(errorPage); + return this; + } + + public DeploymentInfo addErrorPages(final ErrorPage... errorPages) { + this.errorPages.addAll(Arrays.asList(errorPages)); + return this; + } + + public DeploymentInfo addErrorPages(final Collection errorPages) { + this.errorPages.addAll(errorPages); + return this; + } + + public List getErrorPages() { + return Collections.unmodifiableList(errorPages); + } + + public DeploymentInfo addMimeMapping(final MimeMapping mimeMappings) { + this.mimeMappings.add(mimeMappings); + return this; + } + + public DeploymentInfo addMimeMappings(final MimeMapping... mimeMappings) { + this.mimeMappings.addAll(Arrays.asList(mimeMappings)); + return this; + } + + public DeploymentInfo addMimeMappings(final Collection mimeMappings) { + this.mimeMappings.addAll(mimeMappings); + return this; + } + + public List getMimeMappings() { + return Collections.unmodifiableList(mimeMappings); + } + + + public DeploymentInfo addSecurityConstraint(final SecurityConstraint securityConstraint) { + this.securityConstraints.add(securityConstraint); + return this; + } + + public DeploymentInfo addSecurityConstraints(final SecurityConstraint... securityConstraints) { + this.securityConstraints.addAll(Arrays.asList(securityConstraints)); + return this; + } + + public DeploymentInfo addSecurityConstraints(final Collection securityConstraints) { + this.securityConstraints.addAll(securityConstraints); + return this; + } + + public List getSecurityConstraints() { + return Collections.unmodifiableList(securityConstraints); + } + + public Executor getExecutor() { + return executor; + } + + /** + * Sets the executor that will be used to run servlet invocations. If this is null then the XNIO worker pool will be + * used. + *

+ * Individual servlets may use a different executor + *

+ * If this is null then the current executor is used, which is generally the XNIO worker pool + * + * @param executor The executor + * @see ServletInfo#executor + */ + public DeploymentInfo setExecutor(final Executor executor) { + this.executor = executor; + return this; + } + + public Executor getAsyncExecutor() { + return asyncExecutor; + } + + /** + * Sets the executor that is used to run async tasks. + *

+ * If this is null then {@link #executor} is used, if this is also null then the default is used + * + * @param asyncExecutor The executor + */ + public DeploymentInfo setAsyncExecutor(final Executor asyncExecutor) { + this.asyncExecutor = asyncExecutor; + return this; + } + + public File getTempDir() { + return tempDir; + } + + public DeploymentInfo setTempDir(final File tempDir) { + this.tempDir = tempDir; + return this; + } + + public boolean isIgnoreFlush() { + return ignoreFlush; + } + + public DeploymentInfo setIgnoreFlush(boolean ignoreFlush) { + this.ignoreFlush = ignoreFlush; + return this; + } + + public JspConfigDescriptor getJspConfigDescriptor() { + return jspConfigDescriptor; + } + + public DeploymentInfo setJspConfigDescriptor(JspConfigDescriptor jspConfigDescriptor) { + this.jspConfigDescriptor = jspConfigDescriptor; + return this; + } + + public DefaultServletConfig getDefaultServletConfig() { + return defaultServletConfig; + } + + public DeploymentInfo setDefaultServletConfig(final DefaultServletConfig defaultServletConfig) { + this.defaultServletConfig = defaultServletConfig; + return this; + } + + public DeploymentInfo addLocaleCharsetMapping(final String locale, final String charset) { + localeCharsetMapping.put(locale, charset); + return this; + } + + public Map getLocaleCharsetMapping() { + return localeCharsetMapping; + } + + public SessionManagerFactory getSessionManagerFactory() { + return sessionManagerFactory; + } + + public DeploymentInfo setSessionManagerFactory(final SessionManagerFactory sessionManagerFactory) { + this.sessionManagerFactory = sessionManagerFactory; + return this; + } + + public LoginConfig getLoginConfig() { + return loginConfig; + } + + public DeploymentInfo setLoginConfig(LoginConfig loginConfig) { + this.loginConfig = loginConfig; + return this; + } + + public IdentityManager getIdentityManager() { + return identityManager; + } + + public DeploymentInfo setIdentityManager(IdentityManager identityManager) { + this.identityManager = identityManager; + return this; + } + + public ConfidentialPortManager getConfidentialPortManager() { + return confidentialPortManager; + } + + public DeploymentInfo setConfidentialPortManager(ConfidentialPortManager confidentialPortManager) { + this.confidentialPortManager = confidentialPortManager; + return this; + } + + public DeploymentInfo addSecurityRole(final String role) { + this.securityRoles.add(role); + return this; + } + + public DeploymentInfo addSecurityRoles(final String... roles) { + this.securityRoles.addAll(Arrays.asList(roles)); + return this; + } + + public DeploymentInfo addSecurityRoles(final Collection roles) { + this.securityRoles.addAll(roles); + return this; + } + + public Set getSecurityRoles() { + return Collections.unmodifiableSet(securityRoles); + } + + /** + * Adds an outer handler wrapper. This handler will be run after the servlet initial handler, + * but before any other handlers. These are only run on REQUEST invocations, they + * are not invoked on a FORWARD or INCLUDE. + * + * @param wrapper The wrapper + */ + public DeploymentInfo addOuterHandlerChainWrapper(final HandlerWrapper wrapper) { + outerHandlerChainWrappers.add(wrapper); + return this; + } + + public List getOuterHandlerChainWrappers() { + return Collections.unmodifiableList(outerHandlerChainWrappers); + } + + /** + * Adds an inner handler chain wrapper. This handler will be run after the security handler, + * but before any other servlet handlers, and will be run for every request + * + * @param wrapper The wrapper + */ + public DeploymentInfo addInnerHandlerChainWrapper(final HandlerWrapper wrapper) { + innerHandlerChainWrappers.add(wrapper); + return this; + } + + public List getInnerHandlerChainWrappers() { + return Collections.unmodifiableList(innerHandlerChainWrappers); + } + + public DeploymentInfo addInitialHandlerChainWrapper(final HandlerWrapper wrapper) { + initialHandlerChainWrappers.add(wrapper); + return this; + } + + public List getInitialHandlerChainWrappers() { + return Collections.unmodifiableList(initialHandlerChainWrappers); + } + + public DeploymentInfo addNotificationReceiver(final NotificationReceiver notificationReceiver) { + this.notificationReceivers.add(notificationReceiver); + return this; + } + + public DeploymentInfo addNotificactionReceivers(final NotificationReceiver... notificationReceivers) { + this.notificationReceivers.addAll(Arrays.asList(notificationReceivers)); + return this; + } + + public DeploymentInfo addNotificationReceivers(final Collection notificationReceivers) { + this.notificationReceivers.addAll(notificationReceivers); + return this; + } + + public List getNotificationReceivers() { + return Collections.unmodifiableList(notificationReceivers); + } + + public ConcurrentMap getServletContextAttributeBackingMap() { + return servletContextAttributeBackingMap; + } + + /** + * Sets the map that will be used by the ServletContext implementation to store attributes. + *

+ * This should usuablly be null, in which case Undertow will create a new map. This is only + * used in situations where you want multiple deployments to share the same servlet context + * attributes. + * + * @param servletContextAttributeBackingMap + * The backing map + */ + public DeploymentInfo setServletContextAttributeBackingMap(final ConcurrentMap servletContextAttributeBackingMap) { + this.servletContextAttributeBackingMap = servletContextAttributeBackingMap; + return this; + } + + public ServletSessionConfig getServletSessionConfig() { + return servletSessionConfig; + } + + public DeploymentInfo setServletSessionConfig(final ServletSessionConfig servletSessionConfig) { + this.servletSessionConfig = servletSessionConfig; + return this; + } + + /** + * @return the host name + */ + public String getHostName() { + return hostName; + } + + public DeploymentInfo setHostName(final String hostName) { + this.hostName = hostName; + return this; + } + + public boolean isDenyUncoveredHttpMethods() { + return denyUncoveredHttpMethods; + } + + public DeploymentInfo setDenyUncoveredHttpMethods(final boolean denyUncoveredHttpMethods) { + this.denyUncoveredHttpMethods = denyUncoveredHttpMethods; + return this; + } + + public ServletStackTraces getServletStackTraces() { + return servletStackTraces; + } + + public DeploymentInfo setServletStackTraces(ServletStackTraces servletStackTraces) { + this.servletStackTraces = servletStackTraces; + return this; + } + + public boolean isInvalidateSessionOnLogout() { + return invalidateSessionOnLogout; + } + + public DeploymentInfo setInvalidateSessionOnLogout(boolean invalidateSessionOnLogout) { + this.invalidateSessionOnLogout = invalidateSessionOnLogout; + return this; + } + + public int getDefaultCookieVersion() { + return defaultCookieVersion; + } + + public DeploymentInfo setDefaultCookieVersion(int defaultCookieVersion) { + this.defaultCookieVersion = defaultCookieVersion; + return this; + } + + public SessionPersistenceManager getSessionPersistenceManager() { + return sessionPersistenceManager; + } + + public DeploymentInfo setSessionPersistenceManager(SessionPersistenceManager sessionPersistenceManager) { + this.sessionPersistenceManager = sessionPersistenceManager; + return this; + } + + public AuthorizationManager getAuthorizationManager() { + return authorizationManager; + } + + public DeploymentInfo setAuthorizationManager(AuthorizationManager authorizationManager) { + this.authorizationManager = authorizationManager; + return this; + } + + public DeploymentInfo addPrincipalVsRoleMapping(final String principal, final String mapping) { + Set set = principalVersusRolesMap.get(principal); + if (set == null) { + principalVersusRolesMap.put(principal, set = new HashSet()); + } + set.add(mapping); + return this; + } + + public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final String... mappings) { + Set set = principalVersusRolesMap.get(principal); + if (set == null) { + principalVersusRolesMap.put(principal, set = new HashSet()); + } + set.addAll(Arrays.asList(mappings)); + return this; + } + + public DeploymentInfo addPrincipalVsRoleMappings(final String principal, final Collection mappings) { + Set set = principalVersusRolesMap.get(principal); + if (set == null) { + principalVersusRolesMap.put(principal, set = new HashSet()); + } + set.addAll(mappings); + return this; + } + + public Map> getPrincipalVersusRolesMap() { + return Collections.unmodifiableMap(principalVersusRolesMap); + } + + /** + * Removes all configured authentication mechanisms from the deployment. + * + * @return this deployment info + */ + public DeploymentInfo clearLoginMethods() { + if(loginConfig != null) { + loginConfig.getAuthMethods().clear(); + } + return this; + } + + /** + * Adds an authentication mechanism directly to the deployment. This mechanism will be first in the list. + * + * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)} + * and allow the user to configure the methods they want by name. + * + * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns + * and authentication mechanism, and then adding it to the login config list. + * + * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}. + * + * @param name The authentication mechanism name + * @param mechanism The mechanism + * @return this deployment info + */ + public DeploymentInfo addFirstAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) { + authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism)); + if(loginConfig == null) { + loginConfig = new LoginConfig(null); + } + loginConfig.addFirstAuthMethod(new AuthMethodConfig(name)); + return this; + } + + /** + * Adds an authentication mechanism directly to the deployment. This mechanism will be last in the list. + * + * In general you should just use {@link #addAuthenticationMechanism(String, io.undertow.security.api.AuthenticationMechanismFactory)} + * and allow the user to configure the methods they want by name. + * + * This method is essentially a convenience method, if is the same as registering a factory under the provided name that returns + * and authentication mechanism, and then adding it to the login config list. + * + * If you want your mechanism to be the only one in the deployment you should first invoke {@link #clearLoginMethods()}. + * + * @param name The authentication mechanism name + * @param mechanism The mechanism + * @return + */ + public DeploymentInfo addLastAuthenticationMechanism(final String name, final AuthenticationMechanism mechanism) { + authenticationMechanisms.put(name, new ImmediateAuthenticationMechanismFactory(mechanism)); + if(loginConfig == null) { + loginConfig = new LoginConfig(null); + } + loginConfig.addLastAuthMethod(new AuthMethodConfig(name)); + return this; + } + + /** + * Adds an authentication mechanism. The name is case insenstive, and will be converted to uppercase internally. + * + * @param name The name + * @param factory The factory + * @return + */ + public DeploymentInfo addAuthenticationMechanism(final String name, final AuthenticationMechanismFactory factory) { + authenticationMechanisms.put(name.toUpperCase(Locale.US), factory); + return this; + } + + public Map getAuthenticationMechanisms() { + return Collections.unmodifiableMap(authenticationMechanisms); + } + + /** + * Returns true if the specified mechanism is present in the login config + * @param mechanismName The mechanism name + * @return true if the mechanism is enabled + */ + public boolean isAuthenticationMechanismPresent(final String mechanismName) { + if(loginConfig != null) { + for(AuthMethodConfig method : loginConfig.getAuthMethods()) { + if(method.getName().equalsIgnoreCase(mechanismName)) { + return true; + } + } + } + return false; + } + + /** + * Adds an additional servlet extension to the deployment. Servlet extensions are generally discovered + * using META-INF/services entries, however this may not be practical in all environments. + * @param servletExtension The servlet extension + * @return this + */ + public DeploymentInfo addServletExtension(final ServletExtension servletExtension) { + this.servletExtensions.add(servletExtension); + return this; + } + + public List getServletExtensions() { + return servletExtensions; + } + + public AuthenticationMechanism getJaspiAuthenticationMechanism() { + return jaspiAuthenticationMechanism; + } + + public void setJaspiAuthenticationMechanism(AuthenticationMechanism jaspiAuthenticationMechanism) { + this.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism; + } + + public SecurityContextFactory getSecurityContextFactory() { + return this.securityContextFactory; + } + + public void setSecurityContextFactory(final SecurityContextFactory securityContextFactory) { + this.securityContextFactory = securityContextFactory; + } + + public String getServerName() { + return serverName; + } + + public DeploymentInfo setServerName(String serverName) { + this.serverName = serverName; + return this; + } + + public DeploymentInfo setMetricsCollector(MetricsCollector metricsCollector){ + this.metricsCollector = metricsCollector; + return this; + } + + public MetricsCollector getMetricsCollector() { + return metricsCollector; + } + + public SessionConfigWrapper getSessionConfigWrapper() { + return sessionConfigWrapper; + } + + public DeploymentInfo setSessionConfigWrapper(SessionConfigWrapper sessionConfigWrapper) { + this.sessionConfigWrapper = sessionConfigWrapper; + return this; + } + + public boolean isDisableCachingForSecuredPages() { + return disableCachingForSecuredPages; + } + + public void setDisableCachingForSecuredPages(boolean disableCachingForSecuredPages) { + this.disableCachingForSecuredPages = disableCachingForSecuredPages; + } + + @Override + public DeploymentInfo clone() { + final DeploymentInfo info = new DeploymentInfo() + .setClassLoader(classLoader) + .setContextPath(contextPath) + .setResourceManager(resourceManager) + .setMajorVersion(majorVersion) + .setMinorVersion(minorVersion) + .setDeploymentName(deploymentName) + .setClassIntrospecter(classIntrospecter); + + for (Map.Entry e : servlets.entrySet()) { + info.addServlet(e.getValue().clone()); + } + + for (Map.Entry e : filters.entrySet()) { + info.addFilter(e.getValue().clone()); + } + info.displayName = displayName; + info.filterUrlMappings.addAll(filterUrlMappings); + info.filterServletNameMappings.addAll(filterServletNameMappings); + info.listeners.addAll(listeners); + info.servletContainerInitializers.addAll(servletContainerInitializers); + info.threadSetupActions.addAll(threadSetupActions); + info.initParameters.putAll(initParameters); + info.servletContextAttributes.putAll(servletContextAttributes); + info.welcomePages.addAll(welcomePages); + info.errorPages.addAll(errorPages); + info.mimeMappings.addAll(mimeMappings); + info.executor = executor; + info.asyncExecutor = asyncExecutor; + info.tempDir = tempDir; + info.jspConfigDescriptor = jspConfigDescriptor; + info.defaultServletConfig = defaultServletConfig; + info.localeCharsetMapping.putAll(localeCharsetMapping); + info.sessionManagerFactory = sessionManagerFactory; + if (loginConfig != null) { + info.loginConfig = loginConfig.clone(); + } + info.identityManager = identityManager; + info.confidentialPortManager = confidentialPortManager; + info.defaultEncoding = defaultEncoding; + info.urlEncoding = urlEncoding; + info.securityConstraints.addAll(securityConstraints); + info.outerHandlerChainWrappers.addAll(outerHandlerChainWrappers); + info.innerHandlerChainWrappers.addAll(innerHandlerChainWrappers); + info.initialHandlerChainWrappers.addAll(initialHandlerChainWrappers); + info.securityRoles.addAll(securityRoles); + info.notificationReceivers.addAll(notificationReceivers); + info.allowNonStandardWrappers = allowNonStandardWrappers; + info.defaultSessionTimeout = defaultSessionTimeout; + info.servletContextAttributeBackingMap = servletContextAttributeBackingMap; + info.servletSessionConfig = servletSessionConfig; + info.hostName = hostName; + info.denyUncoveredHttpMethods = denyUncoveredHttpMethods; + info.servletStackTraces = servletStackTraces; + info.invalidateSessionOnLogout = invalidateSessionOnLogout; + info.defaultCookieVersion = defaultCookieVersion; + info.sessionPersistenceManager = sessionPersistenceManager; + info.principalVersusRolesMap.putAll(principalVersusRolesMap); + info.ignoreFlush = ignoreFlush; + info.authorizationManager = authorizationManager; + info.authenticationMechanisms.putAll(authenticationMechanisms); + info.servletExtensions.addAll(servletExtensions); + info.jaspiAuthenticationMechanism = jaspiAuthenticationMechanism; + info.securityContextFactory = securityContextFactory; + info.serverName = serverName; + info.metricsCollector = metricsCollector; + info.sessionConfigWrapper = sessionConfigWrapper; + info.eagerFilterInit = eagerFilterInit; + info.disableCachingForSecuredPages = disableCachingForSecuredPages; + return info; + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentManager.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentManager.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/DeploymentManager.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,64 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import javax.servlet.ServletException; + +import io.undertow.server.HttpHandler; + +/** + * Manager that can be used to deploy and undeploy a servlet deployment. + * + * @author Stuart Douglas + */ +public interface DeploymentManager { + + /** + * Perform the initial deployment. + * + * The builds all the internal metadata needed to support the servlet deployment, but will not actually start + * any servlets + * + */ + void deploy(); + + /** + * Starts the container. Any Servlets with init on startup will be created here. This method returns the servlet + * path handler, which must then be added into the appropriate place in the path handler tree. + * + */ + HttpHandler start() throws ServletException; + + void stop() throws ServletException; + + void undeploy(); + + State getState(); + + /** + * + */ + Deployment getDeployment(); + + public static enum State { + UNDEPLOYED, + DEPLOYED, + STARTED; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ErrorPage.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ErrorPage.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ErrorPage.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,61 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +/** + * A servlet error page mapping + * + * + * @author Stuart Douglas + */ +public class ErrorPage { + + private final String location; + private final Integer errorCode; + private final Class exceptionType; + + public ErrorPage(final String location, final Class exceptionType) { + this.location = location; + this.errorCode = null; + this.exceptionType = exceptionType; + } + public ErrorPage(final String location, final int errorCode) { + this.location = location; + this.errorCode = errorCode; + this.exceptionType = null; + } + + public ErrorPage(final String location) { + this.location = location; + this.errorCode = null; + this.exceptionType = null; + } + + public String getLocation() { + return location; + } + + public Integer getErrorCode() { + return errorCode; + } + + public Class getExceptionType() { + return exceptionType; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/FilterInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/FilterInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/FilterInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,123 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.lang.reflect.Constructor; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.Filter; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.util.ConstructorInstanceFactory; + +/** + * @author Stuart Douglas + */ +public class FilterInfo implements Cloneable { + + private final Class filterClass; + private final String name; + private final InstanceFactory instanceFactory; + + private final Map initParams = new HashMap(); + private volatile boolean asyncSupported; + + + public FilterInfo(final String name, final Class filterClass) { + if (name == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); + } + if (filterClass == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("filterClass", "Filter", name); + } + if (!Filter.class.isAssignableFrom(filterClass)) { + throw UndertowServletMessages.MESSAGES.filterMustImplementFilter(name, filterClass); + } + try { + final Constructor ctor = (Constructor) filterClass.getDeclaredConstructor(); + ctor.setAccessible(true); + this.instanceFactory = new ConstructorInstanceFactory(ctor); + this.name = name; + this.filterClass = filterClass; + } catch (NoSuchMethodException e) { + throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Filter", filterClass); + } + } + + + public FilterInfo(final String name, final Class filterClass, final InstanceFactory instanceFactory) { + if (name == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); + } + if (filterClass == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("filterClass", "Filter", name); + } + if (!Filter.class.isAssignableFrom(filterClass)) { + throw UndertowServletMessages.MESSAGES.filterMustImplementFilter(name, filterClass); + } + this.instanceFactory = instanceFactory; + this.name = name; + this.filterClass = filterClass; + } + + public void validate() { + //TODO + } + + @Override + public FilterInfo clone() { + FilterInfo info = new FilterInfo(name, filterClass, instanceFactory) + .setAsyncSupported(asyncSupported); + info.initParams.putAll(initParams); + return info; + } + + public Class getFilterClass() { + return filterClass; + } + + public String getName() { + return name; + } + public InstanceFactory getInstanceFactory() { + return instanceFactory; + } + + public FilterInfo addInitParam(final String name, final String value) { + initParams.put(name, value); + return this; + } + + public Map getInitParams() { + return Collections.unmodifiableMap(initParams); + } + + public boolean isAsyncSupported() { + return asyncSupported; + } + + public FilterInfo setAsyncSupported(final boolean asyncSupported) { + this.asyncSupported = asyncSupported; + return this; + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/FilterMappingInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/FilterMappingInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/FilterMappingInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,61 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import javax.servlet.DispatcherType; + +/** + * @author Stuart Douglas + */ +public class FilterMappingInfo { + + private final String filterName; + private final MappingType mappingType; + private final String mapping; + private final DispatcherType dispatcher; + + public FilterMappingInfo(final String filterName, final MappingType mappingType, final String mapping, final DispatcherType dispatcher) { + this.filterName = filterName; + this.mappingType = mappingType; + this.mapping = mapping; + this.dispatcher = dispatcher; + } + + public MappingType getMappingType() { + return mappingType; + } + + public String getMapping() { + return mapping; + } + + public DispatcherType getDispatcher() { + return dispatcher; + } + + public String getFilterName() { + return filterName; + } + + public static enum MappingType { + URL, + SERVLET; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/HttpMethodSecurityInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/HttpMethodSecurityInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/HttpMethodSecurityInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,30 @@ +package io.undertow.servlet.api; + +/** + * @author Stuart Douglas + */ +public class HttpMethodSecurityInfo extends SecurityInfo implements Cloneable { + + private volatile String method; + + public String getMethod() { + return method; + } + + public HttpMethodSecurityInfo setMethod(final String method) { + this.method = method; + return this; + } + + @Override + protected HttpMethodSecurityInfo createInstance() { + return new HttpMethodSecurityInfo(); + } + + @Override + public HttpMethodSecurityInfo clone() { + HttpMethodSecurityInfo info = super.clone(); + info.method = method; + return info; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceFactory.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceFactory.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceFactory.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,35 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +/** + * Factory that creates fully injected component instances. + * + * @author Stuart Douglas + */ +public interface InstanceFactory { + + /** + * Factory that creates a fully injected instance. + * + * @return The fully injected instance + */ + InstanceHandle createInstance() throws InstantiationException; + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceHandle.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceHandle.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/InstanceHandle.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,40 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +/** + * A handle for a container managed instance. When the servlet container is + * done with it it should call the {@link #release()} method + * + * @author Stuart Douglas + */ +public interface InstanceHandle { + + /** + * @return The managed instance + * + */ + T getInstance(); + + /** + * releases the instance, uninjecting and calling an pre-destroy methods as appropriate + */ + void release(); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ListenerInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ListenerInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ListenerInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,64 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.lang.reflect.Constructor; +import java.util.EventListener; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.core.ApplicationListeners; +import io.undertow.servlet.util.ConstructorInstanceFactory; + +/** + * @author Stuart Douglas + */ +public class ListenerInfo { + + + private final Class listenerClass; + private final InstanceFactory instanceFactory; + + public ListenerInfo(final Class listenerClass, final InstanceFactory instanceFactory) { + this.listenerClass = listenerClass; + this.instanceFactory = instanceFactory; + if(!ApplicationListeners.isListenerClass(listenerClass)) { + throw UndertowServletMessages.MESSAGES.listenerMustImplementListenerClass(listenerClass); + } + } + + public ListenerInfo(final Class listenerClass) { + this.listenerClass = listenerClass; + + try { + final Constructor ctor = (Constructor) listenerClass.getDeclaredConstructor(); + ctor.setAccessible(true); + this.instanceFactory = new ConstructorInstanceFactory(ctor); + } catch (NoSuchMethodException e) { + throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Listener", listenerClass); + } + } + + public InstanceFactory getInstanceFactory() { + return instanceFactory; + } + + public Class getListenerClass() { + return listenerClass; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/LoginConfig.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/LoginConfig.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/LoginConfig.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,80 @@ +package io.undertow.servlet.api; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stuart Douglas + */ +public class LoginConfig implements Cloneable { + private final LinkedList authMethods = new LinkedList(); + private final String realmName; + private final String loginPage; + private final String errorPage; + + + public LoginConfig(String realmName, String loginPage, String errorPage) { + this.realmName = realmName; + this.loginPage = loginPage; + this.errorPage = errorPage; + } + + public LoginConfig(final String realmName) { + this(realmName, null, null); + } + + public LoginConfig(String mechanismName, String realmName, String loginPage, String errorPage) { + this.realmName = realmName; + this.loginPage = loginPage; + this.errorPage = errorPage; + addFirstAuthMethod(mechanismName); + } + + public LoginConfig(String mechanismName, final String realmName) { + this(mechanismName, realmName, null, null); + } + + public String getRealmName() { + return realmName; + } + + public String getLoginPage() { + return loginPage; + } + + public String getErrorPage() { + return errorPage; + } + + public LoginConfig addFirstAuthMethod(AuthMethodConfig authMethodConfig) { + authMethods.addFirst(authMethodConfig); + return this; + } + + public LoginConfig addLastAuthMethod(AuthMethodConfig authMethodConfig) { + authMethods.addLast(authMethodConfig); + return this; + } + public LoginConfig addFirstAuthMethod(String authMethodConfig) { + authMethods.addFirst(new AuthMethodConfig(authMethodConfig)); + return this; + } + + public LoginConfig addLastAuthMethod(String authMethodConfig) { + authMethods.addLast(new AuthMethodConfig(authMethodConfig)); + return this; + } + + public List getAuthMethods() { + return authMethods; + } + + @Override + public LoginConfig clone() { + LoginConfig lc = new LoginConfig(realmName, loginPage, errorPage); + for(AuthMethodConfig method : authMethods) { + lc.authMethods.add(method.clone()); + } + return lc; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/MetricsCollector.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/MetricsCollector.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/MetricsCollector.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,13 @@ +package io.undertow.servlet.api; + +import io.undertow.server.handlers.MetricsHandler; + +/** + * An interface that can be used to collect Servlet metrics + * + * @author Tomaz Cerar (c) 2014 Red Hat Inc. + */ +public interface MetricsCollector { + + void registerMetric(String servletName, MetricsHandler handler); +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/MimeMapping.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/MimeMapping.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/MimeMapping.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,40 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +/** + * @author Stuart Douglas + */ +public class MimeMapping { + private final String extension; + private final String mimeType; + + public MimeMapping(final String extension, final String mimeType) { + this.extension = extension; + this.mimeType = mimeType; + } + + public String getExtension() { + return extension; + } + + public String getMimeType() { + return mimeType; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityConstraint.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityConstraint.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityConstraint.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,49 @@ +package io.undertow.servlet.api; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author Stuart Douglas + */ +public class SecurityConstraint extends SecurityInfo { + + private final Set webResourceCollections = new HashSet(); + + public Set getWebResourceCollections() { + return Collections.unmodifiableSet(webResourceCollections); + } + + public SecurityConstraint addWebResourceCollection(final WebResourceCollection webResourceCollection) { + this.webResourceCollections.add(webResourceCollection); + return this; + } + + public SecurityConstraint addWebResourceCollections(final WebResourceCollection... webResourceCollection) { + this.webResourceCollections.addAll(Arrays.asList(webResourceCollection)); + return this; + } + + public SecurityConstraint addWebResourceCollections(final List webResourceCollections) { + this.webResourceCollections.addAll(webResourceCollections); + return this; + } + + @Override + protected SecurityConstraint createInstance() { + return new SecurityConstraint(); + } + + @Override + public SecurityConstraint clone() { + SecurityConstraint info = super.clone(); + for (WebResourceCollection wr : webResourceCollections) { + info.addWebResourceCollection(wr.clone()); + } + return info; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,104 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Stuart Douglas + */ +public class SecurityInfo implements Cloneable { + + /** + * Equivalent to {@see ServletSecurity.EmptyRoleSemantic} but with an additional mode to require authentication but no role + * check. + */ + public enum EmptyRoleSemantic { + + /** + * Permit access to the resource without requiring authentication or role membership. + */ + PERMIT, + + /** + * Deny access to the resource regardless of the authentication state. + */ + DENY, + + /** + * Mandate authentication but authorize access as no roles to check against. + */ + AUTHENTICATE; + + } + + private volatile EmptyRoleSemantic emptyRoleSemantic = EmptyRoleSemantic.DENY; + private final Set rolesAllowed = new HashSet(); + private volatile TransportGuaranteeType transportGuaranteeType = TransportGuaranteeType.NONE; + + public EmptyRoleSemantic getEmptyRoleSemantic() { + return emptyRoleSemantic; + } + + public T setEmptyRoleSemantic(final EmptyRoleSemantic emptyRoleSemantic) { + this.emptyRoleSemantic = emptyRoleSemantic; + return (T)this; + } + + public TransportGuaranteeType getTransportGuaranteeType() { + return transportGuaranteeType; + } + + public T setTransportGuaranteeType(final TransportGuaranteeType transportGuaranteeType) { + this.transportGuaranteeType = transportGuaranteeType; + return (T) this; + } + + public T addRoleAllowed(final String role) { + this.rolesAllowed.add(role); + return (T) this; + } + + public T addRolesAllowed(final String ... roles) { + this.rolesAllowed.addAll(Arrays.asList(roles)); + return (T) this; + } + public T addRolesAllowed(final Collection roles) { + this.rolesAllowed.addAll(roles); + return (T) this; + } + public Set getRolesAllowed() { + return new HashSet(rolesAllowed); + } + + @Override + public T clone() { + final SecurityInfo info = createInstance(); + info.emptyRoleSemantic = emptyRoleSemantic; + info.transportGuaranteeType = transportGuaranteeType; + info.rolesAllowed.addAll(rolesAllowed); + return (T) info; + } + + protected T createInstance() { + return (T) new SecurityInfo(); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityRoleRef.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityRoleRef.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SecurityRoleRef.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,46 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import io.undertow.servlet.UndertowServletMessages; + +/** + * @author Stuart Douglas + */ +public class SecurityRoleRef { + + private final String role; + private final String linkedRole; + + public SecurityRoleRef(final String role, final String linkedRole) { + if(role == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("role"); + } + this.role = role; + this.linkedRole = linkedRole; + } + + public String getRole() { + return role; + } + + public String getLinkedRole() { + return linkedRole; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainer.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainer.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainer.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,51 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.util.Collection; + +import io.undertow.servlet.core.ServletContainerImpl; + +/** + * @author Stuart Douglas + */ +public interface ServletContainer { + + /** + * + * @return The names of the deployments in this container + */ + Collection listDeployments(); + + DeploymentManager addDeployment(DeploymentInfo deployment); + + DeploymentManager getDeployment(String deploymentName); + + void removeDeployment(DeploymentInfo deploymentInfo); + + DeploymentManager getDeploymentByPath(String uripath); + + public static class Factory { + + public static ServletContainer newInstance() { + return new ServletContainerImpl(); + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainerInitializerInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainerInitializerInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletContainerInitializerInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,59 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.util.Set; + +import javax.servlet.ServletContainerInitializer; + +/** + * @author Stuart Douglas + */ +public class ServletContainerInitializerInfo { + + private final Class servletContainerInitializerClass; + private final InstanceFactory instanceFactory; + private final Set> handlesTypes; + + public ServletContainerInitializerInfo(final Class servletContainerInitializerClass, final InstanceFactory instanceFactory, final Set> handlesTypes) { + this.servletContainerInitializerClass = servletContainerInitializerClass; + this.instanceFactory = instanceFactory; + this.handlesTypes = handlesTypes; + } + + public Class getServletContainerInitializerClass() { + return servletContainerInitializerClass; + } + + /** + * Returns the actual types present in the deployment that are handled by this ServletContainerInitializer. + * + * (i.e. not the types in the {@link javax.servlet.annotation.HandlesTypes} annotation, but rather actual types + * the container has discovered that meet the criteria) + * + * @return The handled types + */ + public Set> getHandlesTypes() { + return handlesTypes; + } + + public InstanceFactory getInstanceFactory() { + return instanceFactory; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletDispatcher.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletDispatcher.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletDispatcher.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,34 @@ +package io.undertow.servlet.api; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletChain; +import io.undertow.servlet.handlers.ServletPathMatch; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author Stuart Douglas + */ +public interface ServletDispatcher { + /** + * Dispatches a servlet request to the specified servlet path, changing the current path + * @see io.undertow.servlet.handlers.ServletRequestContext + */ + void dispatchToPath(final HttpServerExchange exchange, final ServletPathMatch pathMatch, final DispatcherType dispatcherType) throws Exception; + + /** + * Dispatches a servlet request to the specified servlet, without changing the current path + */ + void dispatchToServlet(final HttpServerExchange exchange, final ServletChain servletChain, final DispatcherType dispatcherType) throws Exception; + + /** + * Dispatches a mock request to the servlet container. + * + * @param request The request + * @param response The response + */ + void dispatchMockRequest(final HttpServletRequest request, final HttpServletResponse response) throws ServletException; +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,282 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.Servlet; + +import io.undertow.server.HandlerWrapper; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.util.ConstructorInstanceFactory; + +/** + * @author Stuart Douglas + */ +public class ServletInfo implements Cloneable { + + private final Class servletClass; + private final String name; + + private final List mappings = new ArrayList(); + private final Map initParams = new HashMap(); + private final List securityRoleRefs = new ArrayList(); + private final List handlerChainWrappers = new ArrayList(); + + private InstanceFactory instanceFactory; + private String jspFile; + private Integer loadOnStartup; + private boolean enabled; + private boolean asyncSupported; + private String runAs; + private MultipartConfigElement multipartConfig; + private ServletSecurityInfo servletSecurityInfo; + private Executor executor; + /** + * If this is true this servlet will not be considered when evaluating welcome file mappings, + * and if the mapped path is a directory a welcome file match will be performed that may result in another servlet + * being selected. + * + * Generally intended to be used by the default and JSP servlet. + */ + private boolean requireWelcomeFileMapping; + + public ServletInfo(final String name, final Class servletClass) { + if (name == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); + } + if (servletClass == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("servletClass", "Servlet", name); + } + if (!Servlet.class.isAssignableFrom(servletClass)) { + throw UndertowServletMessages.MESSAGES.servletMustImplementServlet(name, servletClass); + } + try { + final Constructor ctor = servletClass.getDeclaredConstructor(); + ctor.setAccessible(true); + this.instanceFactory = new ConstructorInstanceFactory(ctor); + this.name = name; + this.servletClass = servletClass; + } catch (NoSuchMethodException e) { + throw UndertowServletMessages.MESSAGES.componentMustHaveDefaultConstructor("Servlet", servletClass); + } + } + + + public ServletInfo(final String name, final Class servletClass, final InstanceFactory instanceFactory) { + if (name == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("name"); + } + if (servletClass == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("servletClass", "Servlet", name); + } + if (!Servlet.class.isAssignableFrom(servletClass)) { + throw UndertowServletMessages.MESSAGES.servletMustImplementServlet(name, servletClass); + } + this.instanceFactory = instanceFactory; + this.name = name; + this.servletClass = servletClass; + } + + public void validate() { + //TODO + } + + @Override + public ServletInfo clone() { + ServletInfo info = new ServletInfo(name, servletClass, instanceFactory) + .setJspFile(jspFile) + .setLoadOnStartup(loadOnStartup) + .setEnabled(enabled) + .setAsyncSupported(asyncSupported) + .setRunAs(runAs) + .setMultipartConfig(multipartConfig) + .setExecutor(executor) + .setRequireWelcomeFileMapping(requireWelcomeFileMapping); + info.mappings.addAll(mappings); + info.initParams.putAll(initParams); + info.securityRoleRefs.addAll(securityRoleRefs); + info.handlerChainWrappers.addAll(handlerChainWrappers); + if (servletSecurityInfo != null) { + info.servletSecurityInfo = servletSecurityInfo.clone(); + } + return info; + } + + public Class getServletClass() { + return servletClass; + } + + public String getName() { + return name; + } + + public void setInstanceFactory(final InstanceFactory instanceFactory) { + if (instanceFactory == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("instanceFactory"); + } + this.instanceFactory = instanceFactory; + } + + public InstanceFactory getInstanceFactory() { + return instanceFactory; + } + + public List getMappings() { + return Collections.unmodifiableList(mappings); + } + + public ServletInfo addMapping(final String mapping) { + mappings.add(mapping); + return this; + } + + + public ServletInfo addMappings(final Collection mappings) { + this.mappings.addAll(mappings); + return this; + } + + + public ServletInfo addMappings(final String... mappings) { + this.mappings.addAll(Arrays.asList(mappings)); + return this; + } + + public ServletInfo addInitParam(final String name, final String value) { + initParams.put(name, value); + return this; + } + + + public Map getInitParams() { + return Collections.unmodifiableMap(initParams); + } + + public String getJspFile() { + return jspFile; + } + + public ServletInfo setJspFile(final String jspFile) { + this.jspFile = jspFile; + return this; + } + + public Integer getLoadOnStartup() { + return loadOnStartup; + } + + public ServletInfo setLoadOnStartup(final Integer loadOnStartup) { + this.loadOnStartup = loadOnStartup; + return this; + } + + public boolean isAsyncSupported() { + return asyncSupported; + } + + public ServletInfo setAsyncSupported(final boolean asyncSupported) { + this.asyncSupported = asyncSupported; + return this; + } + + public boolean isEnabled() { + return enabled; + } + + public ServletInfo setEnabled(final boolean enabled) { + this.enabled = enabled; + return this; + } + + public String getRunAs() { + return runAs; + } + + public ServletInfo setRunAs(final String runAs) { + this.runAs = runAs; + return this; + } + + public MultipartConfigElement getMultipartConfig() { + return multipartConfig; + } + + public ServletInfo setMultipartConfig(final MultipartConfigElement multipartConfig) { + this.multipartConfig = multipartConfig; + return this; + } + + public void addSecurityRoleRef(final String role, final String linkedRole) { + this.securityRoleRefs.add(new SecurityRoleRef(role, linkedRole)); + } + + public List getSecurityRoleRefs() { + return Collections.unmodifiableList(securityRoleRefs); + } + + public ServletInfo addHandlerChainWrapper(final HandlerWrapper wrapper) { + this.handlerChainWrappers.add(wrapper); + return this; + } + + public List getHandlerChainWrappers() { + return Collections.unmodifiableList(handlerChainWrappers); + } + + public ServletSecurityInfo getServletSecurityInfo() { + return servletSecurityInfo; + } + + public ServletInfo setServletSecurityInfo(final ServletSecurityInfo servletSecurityInfo) { + this.servletSecurityInfo = servletSecurityInfo; + return this; + } + + public Executor getExecutor() { + return executor; + } + + public ServletInfo setExecutor(final Executor executor) { + this.executor = executor; + return this; + } + + /** + * + * @return + */ + public boolean isRequireWelcomeFileMapping() { + return requireWelcomeFileMapping; + } + + public ServletInfo setRequireWelcomeFileMapping(boolean requireWelcomeFileMapping) { + this.requireWelcomeFileMapping = requireWelcomeFileMapping; + return this; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSecurityInfo.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSecurityInfo.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSecurityInfo.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,35 @@ +package io.undertow.servlet.api; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Stuart Douglas + */ +public class ServletSecurityInfo extends SecurityInfo implements Cloneable { + + private final List httpMethodSecurityInfo = new ArrayList(); + + @Override + protected ServletSecurityInfo createInstance() { + return new ServletSecurityInfo(); + } + + public ServletSecurityInfo addHttpMethodSecurityInfo(final HttpMethodSecurityInfo info) { + httpMethodSecurityInfo.add(info); + return this; + } + + public List getHttpMethodSecurityInfo() { + return new ArrayList(httpMethodSecurityInfo); + } + + @Override + public ServletSecurityInfo clone() { + ServletSecurityInfo info = super.clone(); + for(HttpMethodSecurityInfo method : httpMethodSecurityInfo) { + info.httpMethodSecurityInfo.add(method.clone()); + } + return info; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSessionConfig.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSessionConfig.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletSessionConfig.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,116 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.util.Set; + +import javax.servlet.SessionTrackingMode; + +/** + * + * Session config that gets + * + * @author Stuart Douglas + */ +public class ServletSessionConfig { + + public static final String DEFAULT_SESSION_ID = "JSESSIONID"; + + private Set sessionTrackingModes; + + private String name = DEFAULT_SESSION_ID; + private String path; + private String domain; + private boolean secure; + private boolean httpOnly; + private int maxAge; + private String comment; + + public String getName() { + return name; + } + + public ServletSessionConfig setName(final String name) { + this.name = name; + return this; + } + + public String getDomain() { + return domain; + } + + public ServletSessionConfig setDomain(final String domain) { + this.domain = domain; + return this; + } + + public String getPath() { + return path; + } + + public ServletSessionConfig setPath(final String path) { + this.path = path; + return this; + } + + public String getComment() { + return comment; + } + + public ServletSessionConfig setComment(final String comment) { + this.comment = comment; + return this; + } + + public boolean isHttpOnly() { + return httpOnly; + } + + public ServletSessionConfig setHttpOnly(final boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + public boolean isSecure() { + return secure; + } + + public ServletSessionConfig setSecure(final boolean secure) { + this.secure = secure; + return this; + } + + public int getMaxAge() { + return maxAge; + } + + public ServletSessionConfig setMaxAge(final int maxAge) { + this.maxAge = maxAge; + return this; + } + + public Set getSessionTrackingModes() { + return sessionTrackingModes; + } + + public ServletSessionConfig setSessionTrackingModes(final Set sessionTrackingModes) { + this.sessionTrackingModes = sessionTrackingModes; + return this; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ServletStackTraces.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ServletStackTraces.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ServletStackTraces.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,22 @@ +package io.undertow.servlet.api; + +/** + * @author Stuart Douglas + */ +public enum ServletStackTraces { + + NONE("none"), + LOCAL_ONLY("local-only"), + ALL("all"); + + private final String value; + + private ServletStackTraces(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SessionConfigWrapper.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SessionConfigWrapper.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SessionConfigWrapper.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,15 @@ +package io.undertow.servlet.api; + +import io.undertow.server.session.SessionConfig; + +/** + * A class that allows the SessionConfig to be wrapped. + * + * This is generally used to append JVM route information to the session ID in clustered environments. + * + * @author Stuart Douglas + */ +public interface SessionConfigWrapper { + + SessionConfig wrap(final SessionConfig sessionConfig, final Deployment deployment); +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SessionManagerFactory.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SessionManagerFactory.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SessionManagerFactory.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import io.undertow.server.session.SessionManager; + +/** + * Factory class used to create a session manager + * + * @author Stuart Douglas + */ +public interface SessionManagerFactory { + + SessionManager createSessionManager(final Deployment deployment); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SessionPersistenceManager.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SessionPersistenceManager.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SessionPersistenceManager.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,40 @@ +package io.undertow.servlet.api; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +/** + * Interface that is used in development mode to support session persistence across redeploys. + * + * This is not intended for production use. Serialization is performed on a best effort basis and errors will be ignored. + * + * @author Stuart Douglas + */ +public interface SessionPersistenceManager { + + void persistSessions(final String deploymentName, Map sessionData); + + Map loadSessionAttributes(final String deploymentName, final ClassLoader classLoader); + + void clear(final String deploymentName); + + public class PersistentSession { + private final Date expiration; + private final Map sessionData; + + public PersistentSession(Date expiration, Map sessionData) { + this.expiration = expiration; + this.sessionData = sessionData; + } + + public Date getExpiration() { + return expiration; + } + + public Map getSessionData() { + return Collections.unmodifiableMap(sessionData); + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/SingleConstraintMatch.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/SingleConstraintMatch.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/SingleConstraintMatch.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,48 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import java.util.Set; + +/** + * Representation of a single security constrain matched for a single request. + * + * When performing any authentication/authorization check every constraint MUST be satisfied for the request to be allowed to + * proceed. + * + * @author Darran Lofthouse + */ +public class SingleConstraintMatch { + + private final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic; + private final Set requiredRoles; + + public SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic emptyRoleSemantic, Set requiredRoles) { + this.emptyRoleSemantic = emptyRoleSemantic; + this.requiredRoles = requiredRoles; + } + + public SecurityInfo.EmptyRoleSemantic getEmptyRoleSemantic() { + return emptyRoleSemantic; + } + + public Set getRequiredRoles() { + return requiredRoles; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/ThreadSetupAction.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/ThreadSetupAction.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/ThreadSetupAction.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.api; + +import io.undertow.server.HttpServerExchange; + +/** + * Interface that can be implemented by classes that need to setup + * and thread local context before a request is processed. + * + * @author Stuart Douglas + */ +public interface ThreadSetupAction { + + /** + * Setup any thread local context + * + * @param exchange The exchange, this may be null + * @return A handle to tear down the request when the invocation is finished, or null + */ + Handle setup(final HttpServerExchange exchange); + + public interface Handle { + void tearDown(); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/TransportGuaranteeType.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/TransportGuaranteeType.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/TransportGuaranteeType.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,11 @@ +package io.undertow.servlet.api; + +/** + * @author Stuart Douglas + */ +public enum TransportGuaranteeType { + NONE, + INTEGRAL, + CONFIDENTIAL, + REJECTED; +} Index: 3rdParty_sources/undertow/io/undertow/servlet/api/WebResourceCollection.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/api/WebResourceCollection.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/api/WebResourceCollection.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,82 @@ +package io.undertow.servlet.api; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Stuart Douglas + */ +public class WebResourceCollection implements Cloneable { + + private final Set httpMethods = new HashSet(); + private final Set httpMethodOmissions = new HashSet(); + private final Set urlPatterns = new HashSet(); + + public WebResourceCollection addHttpMethod(final String s) { + httpMethods.add(s); + return this; + } + + public WebResourceCollection addHttpMethods(final String... s) { + httpMethods.addAll(Arrays.asList(s)); + return this; + } + + public WebResourceCollection addHttpMethods(final Collection s) { + httpMethods.addAll(s); + return this; + } + + public WebResourceCollection addUrlPattern(final String s) { + urlPatterns.add(s); + return this; + } + + public WebResourceCollection addUrlPatterns(final String... s) { + urlPatterns.addAll(Arrays.asList(s)); + return this; + } + + public WebResourceCollection addUrlPatterns(final Collection s) { + urlPatterns.addAll(s); + return this; + } + + public WebResourceCollection addHttpMethodOmission(final String s) { + httpMethodOmissions.add(s); + return this; + } + + public WebResourceCollection addHttpMethodOmissions(final String... s) { + httpMethodOmissions.addAll(Arrays.asList(s)); + return this; + } + + public WebResourceCollection addHttpMethodOmissions(final Collection s) { + httpMethodOmissions.addAll(s); + return this; + } + + public Set getHttpMethodOmissions() { + return httpMethodOmissions; + } + + public Set getUrlPatterns() { + return urlPatterns; + } + + public Set getHttpMethods() { + return httpMethods; + } + + @Override + protected WebResourceCollection clone() { + return new WebResourceCollection() + .addHttpMethodOmissions(httpMethodOmissions) + .addHttpMethods(httpMethods) + .addUrlPatterns(urlPatterns); + + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletRequestAttribute.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletRequestAttribute.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletRequestAttribute.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,58 @@ +package io.undertow.servlet.attribute; + +import io.undertow.attribute.ExchangeAttribute; +import io.undertow.attribute.ExchangeAttributeBuilder; +import io.undertow.attribute.ReadOnlyAttributeException; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * An attribute in the servlet request + * + * @author Stuart Douglas + */ +public class ServletRequestAttribute implements ExchangeAttribute { + + private final String attributeName; + + public ServletRequestAttribute(final String attributeName) { + this.attributeName = attributeName; + } + + @Override + public String readAttribute(final HttpServerExchange exchange) { + ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + if (context != null) { + Object result = context.getServletRequest().getAttribute(attributeName); + if (result != null) { + return result.toString(); + } + } + return null; + } + + @Override + public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { + ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + if (context != null) { + context.getServletRequest().setAttribute(attributeName, newValue); + } + } + + public static final class Builder implements ExchangeAttributeBuilder { + + @Override + public String name() { + return "Servlet request attribute"; + } + + @Override + public ExchangeAttribute build(final String token) { + if (token.startsWith("%{r,") && token.endsWith("}")) { + final String attributeName = token.substring(4, token.length() - 1); + return new ServletRequestAttribute(attributeName); + } + return null; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletSessionAttribute.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletSessionAttribute.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/attribute/ServletSessionAttribute.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,74 @@ +package io.undertow.servlet.attribute; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import io.undertow.attribute.ExchangeAttribute; +import io.undertow.attribute.ExchangeAttributeBuilder; +import io.undertow.attribute.ReadOnlyAttributeException; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * An attribute in the servlet request + * + * @author Stuart Douglas + */ +public class ServletSessionAttribute implements ExchangeAttribute { + + private final String attributeName; + + public ServletSessionAttribute(final String attributeName) { + this.attributeName = attributeName; + } + + @Override + public String readAttribute(final HttpServerExchange exchange) { + ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + if (context != null) { + ServletRequest req = context.getServletRequest(); + if (req instanceof HttpServletRequest) { + HttpSession session = ((HttpServletRequest) req).getSession(false); + if (session != null) { + Object result = session.getAttribute(attributeName); + if (result != null) { + return result.toString(); + } + } + } + } + return null; + } + + @Override + public void writeAttribute(final HttpServerExchange exchange, final String newValue) throws ReadOnlyAttributeException { + ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + if (context != null) { + ServletRequest req = context.getServletRequest(); + if (req instanceof HttpServletRequest) { + HttpSession session = ((HttpServletRequest) req).getSession(false); + if (session != null) { + session.setAttribute(attributeName, newValue); + } + } + } + } + + public static final class Builder implements ExchangeAttributeBuilder { + + @Override + public String name() { + return "Servlet session attribute"; + } + + @Override + public ExchangeAttribute build(final String token) { + if (token.startsWith("%{s,") && token.endsWith("}")) { + final String attributeName = token.substring(4, token.length() - 1); + return new ServletSessionAttribute(attributeName); + } + return null; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ApplicationListeners.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ApplicationListeners.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ApplicationListeners.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,326 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import io.undertow.servlet.UndertowServletLogger; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; +import java.util.ArrayList; +import java.util.List; + +import static io.undertow.servlet.core.ApplicationListeners.ListenerState.DECLARED_LISTENER; +import static io.undertow.servlet.core.ApplicationListeners.ListenerState.PROGRAMATIC_LISTENER; + +/** + * Class that is responsible for invoking application listeners. + *

+ * This class does not perform any context setup, the context must be setup + * before invoking this class. + *

+ * Note that arrays are used instead of lists for performance reasons. + * + * @author Stuart Douglas + */ +public class ApplicationListeners implements Lifecycle { + + + private static final ManagedListener[] EMPTY = {}; + + private static final Class[] LISTENER_CLASSES = {ServletContextListener.class, + ServletContextAttributeListener.class, + ServletRequestListener.class, + ServletRequestAttributeListener.class, + javax.servlet.http.HttpSessionListener.class, + javax.servlet.http.HttpSessionAttributeListener.class, + HttpSessionIdListener.class}; + + private static final ThreadLocal IN_PROGRAMATIC_SC_LISTENER_INVOCATION = new ThreadLocal() { + @Override + protected ListenerState initialValue() { + return ListenerState.NO_LISTENER; + } + }; + + private ServletContext servletContext; + private final List allListeners = new ArrayList(); + private ManagedListener[] servletContextListeners; + private ManagedListener[] servletContextAttributeListeners; + private ManagedListener[] servletRequestListeners; + private ManagedListener[] servletRequestAttributeListeners; + private ManagedListener[] httpSessionListeners; + private ManagedListener[] httpSessionAttributeListeners; + private ManagedListener[] httpSessionIdListeners; + private volatile boolean started = false; + + public ApplicationListeners(final List allListeners, final ServletContext servletContext) { + this.servletContext = servletContext; + servletContextListeners = EMPTY; + servletContextAttributeListeners = EMPTY; + servletRequestListeners = EMPTY; + servletRequestAttributeListeners = EMPTY; + httpSessionListeners = EMPTY; + httpSessionAttributeListeners = EMPTY; + httpSessionIdListeners = EMPTY; + for (final ManagedListener listener : allListeners) { + addListener(listener); + } + } + + public void addListener(final ManagedListener listener) { + if (ServletContextListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = servletContextListeners; + servletContextListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, servletContextListeners, 0, old.length); + servletContextListeners[old.length] = listener; + } + if (ServletContextAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + + ManagedListener[] old = servletContextAttributeListeners; + servletContextAttributeListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, servletContextAttributeListeners, 0, old.length); + servletContextAttributeListeners[old.length] = listener; + } + if (ServletRequestListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = servletRequestListeners; + servletRequestListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, servletRequestListeners, 0, old.length); + servletRequestListeners[old.length] = listener; + } + if (ServletRequestAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = servletRequestAttributeListeners; + servletRequestAttributeListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, servletRequestAttributeListeners, 0, old.length); + servletRequestAttributeListeners[old.length] = listener; + } + if (HttpSessionListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = httpSessionListeners; + httpSessionListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, httpSessionListeners, 0, old.length); + httpSessionListeners[old.length] = listener; + } + if (HttpSessionAttributeListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = httpSessionAttributeListeners; + httpSessionAttributeListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, httpSessionAttributeListeners, 0, old.length); + httpSessionAttributeListeners[old.length] = listener; + } + if (HttpSessionIdListener.class.isAssignableFrom(listener.getListenerInfo().getListenerClass())) { + ManagedListener[] old = httpSessionIdListeners; + httpSessionIdListeners = new ManagedListener[old.length + 1]; + System.arraycopy(old, 0, httpSessionIdListeners, 0, old.length); + httpSessionIdListeners[old.length] = listener; + } + this.allListeners.add(listener); + } + + public void start() { + started = true; + } + + public void stop() { + if (started) { + started = false; + for (final ManagedListener listener : allListeners) { + listener.stop(); + } + } + } + + @Override + public boolean isStarted() { + return started; + } + + public void contextInitialized() { + //new listeners can be added here, so we don't use an iterator + final ServletContextEvent event = new ServletContextEvent(servletContext); + for (int i = 0; i < servletContextListeners.length; ++i) { + ManagedListener listener = servletContextListeners[i]; + IN_PROGRAMATIC_SC_LISTENER_INVOCATION.set(listener.isProgramatic() ? PROGRAMATIC_LISTENER : DECLARED_LISTENER); + try { + this.get(listener).contextInitialized(event); + } finally { + IN_PROGRAMATIC_SC_LISTENER_INVOCATION.remove(); + } + } + } + + public void contextDestroyed() { + final ServletContextEvent event = new ServletContextEvent(servletContext); + for (int i = servletContextListeners.length - 1; i >= 0; --i) { + ManagedListener listener = servletContextListeners[i]; + try { + this.get(listener).contextDestroyed(event); + } catch (Exception e) { + UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("contextDestroyed", listener.getListenerInfo().getListenerClass(), e); + } + } + } + + public void servletContextAttributeAdded(final String name, final Object value) { + final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); + for (int i = 0; i < servletContextAttributeListeners.length; ++i) { + this.get(servletContextAttributeListeners[i]).attributeAdded(sre); + } + } + + public void servletContextAttributeRemoved(final String name, final Object value) { + final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); + for (int i = 0; i < servletContextAttributeListeners.length; ++i) { + this.get(servletContextAttributeListeners[i]).attributeRemoved(sre); + } + } + + public void servletContextAttributeReplaced(final String name, final Object value) { + final ServletContextAttributeEvent sre = new ServletContextAttributeEvent(servletContext, name, value); + for (int i = 0; i < servletContextAttributeListeners.length; ++i) { + this.get(servletContextAttributeListeners[i]).attributeReplaced(sre); + } + } + + public void requestInitialized(final ServletRequest request) { + final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); + for (int i = 0; i < servletRequestListeners.length; ++i) { + this.get(servletRequestListeners[i]).requestInitialized(sre); + } + } + + public void requestDestroyed(final ServletRequest request) { + final ServletRequestEvent sre = new ServletRequestEvent(servletContext, request); + for (int i = servletRequestListeners.length - 1; i >= 0; --i) { + ManagedListener listener = servletRequestListeners[i]; + try { + this.get(listener).requestDestroyed(sre); + } catch (Exception e) { + UndertowServletLogger.REQUEST_LOGGER.errorInvokingListener("requestDestroyed", listener.getListenerInfo().getListenerClass(), e); + } + } + } + + public void servletRequestAttributeAdded(final HttpServletRequest request, final String name, final Object value) { + final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); + for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { + this.get(servletRequestAttributeListeners[i]).attributeAdded(sre); + } + } + + public void servletRequestAttributeRemoved(final HttpServletRequest request, final String name, final Object value) { + final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); + for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { + this.get(servletRequestAttributeListeners[i]).attributeRemoved(sre); + } + } + + public void servletRequestAttributeReplaced(final HttpServletRequest request, final String name, final Object value) { + final ServletRequestAttributeEvent sre = new ServletRequestAttributeEvent(servletContext, request, name, value); + for (int i = 0; i < servletRequestAttributeListeners.length; ++i) { + this.get(servletRequestAttributeListeners[i]).attributeReplaced(sre); + } + } + + public void sessionCreated(final HttpSession session) { + final HttpSessionEvent sre = new HttpSessionEvent(session); + for (int i = 0; i < httpSessionListeners.length; ++i) { + this.get(httpSessionListeners[i]).sessionCreated(sre); + } + } + + public void sessionDestroyed(final HttpSession session) { + final HttpSessionEvent sre = new HttpSessionEvent(session); + for (int i = httpSessionListeners.length - 1; i >= 0; --i) { + ManagedListener listener = httpSessionListeners[i]; + this.get(listener).sessionDestroyed(sre); + } + } + + public void httpSessionAttributeAdded(final HttpSession session, final String name, final Object value) { + final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); + for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { + this.get(httpSessionAttributeListeners[i]).attributeAdded(sre); + } + } + + public void httpSessionAttributeRemoved(final HttpSession session, final String name, final Object value) { + final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); + for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { + this.get(httpSessionAttributeListeners[i]).attributeRemoved(sre); + } + } + + public void httpSessionAttributeReplaced(final HttpSession session, final String name, final Object value) { + final HttpSessionBindingEvent sre = new HttpSessionBindingEvent(session, name, value); + for (int i = 0; i < httpSessionAttributeListeners.length; ++i) { + this.get(httpSessionAttributeListeners[i]).attributeReplaced(sre); + } + } + + public void httpSessionIdChanged(final HttpSession session, final String oldSessionId) { + final HttpSessionEvent sre = new HttpSessionEvent(session); + for (int i = 0; i < httpSessionIdListeners.length; ++i) { + this.get(httpSessionIdListeners[i]).sessionIdChanged(sre, oldSessionId); + } + } + + private T get(final ManagedListener listener) { + return (T) listener.instance(); + } + + /** + * returns true if this is in in a + */ + public static ListenerState listenerState() { + return IN_PROGRAMATIC_SC_LISTENER_INVOCATION.get(); + } + + /** + * @param clazz The potential listener class + * @return true if the provided class is a valid listener class + */ + public static boolean isListenerClass(final Class clazz) { + for (Class c : LISTENER_CLASSES) { + if (c.isAssignableFrom(clazz)) { + return true; + } + } + return false; + } + + public static enum ListenerState { + NO_LISTENER, + DECLARED_LISTENER, + PROGRAMATIC_LISTENER, + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/BlockingWriterSenderImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/BlockingWriterSenderImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/BlockingWriterSenderImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,276 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.io.EOFException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +import javax.servlet.DispatcherType; + +import io.undertow.UndertowMessages; +import io.undertow.io.IoCallback; +import io.undertow.io.Sender; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; +import org.xnio.IoUtils; + +/** + * A sender that uses a print writer. + * + * In general this should never be used. It exists for the edge case where a filter has called + * getWriter() and then the default servlet is being used to serve a text file. + * + * @author Stuart Douglas + */ +public class BlockingWriterSenderImpl implements Sender { + + /** + * TODO: we should be used pooled buffers + */ + public static final int BUFFER_SIZE = 128; + + private final CharsetDecoder charsetDecoder; + private final HttpServerExchange exchange; + private final PrintWriter writer; + + private FileChannel pendingFile; + private boolean inCall; + private String next; + private IoCallback queuedCallback; + + public BlockingWriterSenderImpl(final HttpServerExchange exchange, final PrintWriter writer, final String charset) { + this.exchange = exchange; + this.writer = writer; + this.charsetDecoder = Charset.forName(charset).newDecoder(); + } + + @Override + public void send(final ByteBuffer buffer, final IoCallback callback) { + if (inCall) { + queue(new ByteBuffer[]{buffer}, callback); + return; + } + if (writeBuffer(buffer, callback)) { + invokeOnComplete(callback); + } + } + + + @Override + public void send(final ByteBuffer[] buffer, final IoCallback callback) { + if (inCall) { + queue(buffer, callback); + return; + } + for (ByteBuffer b : buffer) { + if (!writeBuffer(b, callback)) { + return; + } + } + invokeOnComplete(callback); + } + + @Override + public void send(final String data, final IoCallback callback) { + if (inCall) { + queue(data, callback); + return; + } + writer.write(data); + + if (writer.checkError()) { + callback.onException(exchange, this, new IOException()); + } else { + invokeOnComplete(callback); + } + } + + @Override + public void send(final ByteBuffer buffer) { + send(buffer, IoCallback.END_EXCHANGE); + } + + @Override + public void send(final ByteBuffer[] buffer) { + send(buffer, IoCallback.END_EXCHANGE); + } + + @Override + public void send(final String data, final Charset charset, final IoCallback callback) { + if (inCall) { + queue(new ByteBuffer[]{ByteBuffer.wrap(data.getBytes(charset))}, callback); + return; + } + writer.write(data); + if (writer.checkError()) { + callback.onException(exchange, this, new IOException()); + } else { + invokeOnComplete(callback); + } + } + + @Override + public void send(final String data) { + send(data, IoCallback.END_EXCHANGE); + } + + @Override + public void send(final String data, final Charset charset) { + send(data, charset, IoCallback.END_EXCHANGE); + } + + @Override + public void transferFrom(FileChannel source, IoCallback callback) { + if (inCall) { + queue(source, callback); + return; + } + performTransfer(source, callback); + } + + private void performTransfer(FileChannel source, IoCallback callback) { + + ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); + try { + long pos = source.position(); + long size = source.size(); + while (size - pos > 0) { + int ret = source.read(buffer); + if (ret <= 0) { + break; + } + pos += ret; + buffer.flip(); + if (!writeBuffer(buffer, callback)) { + return; + } + buffer.clear(); + } + + if (pos != size) { + throw new EOFException("Unexpected EOF reading file"); + } + + } catch (IOException e) { + callback.onException(exchange, this, e); + } + invokeOnComplete(callback); + } + + + @Override + public void close(final IoCallback callback) { + writer.close(); + invokeOnComplete(callback); + } + + @Override + public void close() { + if(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType() != DispatcherType.INCLUDE) { + IoUtils.safeClose(writer); + } + } + + + private boolean writeBuffer(final ByteBuffer buffer, final IoCallback callback) { + StringBuilder builder = new StringBuilder(); + try { + builder.append(charsetDecoder.decode(buffer)); + } catch (CharacterCodingException e) { + callback.onException(exchange, this, e); + return false; + } + String data = builder.toString(); + writer.write(data); + if (writer.checkError()) { + callback.onException(exchange, this, new IOException()); + return false; + } + return true; + } + + + private void invokeOnComplete(final IoCallback callback) { + inCall = true; + try { + callback.onComplete(exchange, this); + } finally { + inCall = false; + } + while (next != null) { + String next = this.next; + IoCallback queuedCallback = this.queuedCallback; + this.next = null; + this.queuedCallback = null; + writer.write(next); + if (writer.checkError()) { + queuedCallback.onException(exchange, this, new IOException()); + } else { + inCall = true; + try { + queuedCallback.onComplete(exchange, this); + } finally { + inCall = false; + } + } + } + } + + private void queue(final ByteBuffer[] byteBuffers, final IoCallback ioCallback) { + //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely + if (next != null || pendingFile != null) { + throw UndertowMessages.MESSAGES.dataAlreadyQueued(); + } + StringBuilder builder = new StringBuilder(); + for (ByteBuffer buffer : byteBuffers) { + try { + builder.append(charsetDecoder.decode(buffer)); + } catch (CharacterCodingException e) { + ioCallback.onException(exchange, this, e); + return; + } + } + this.next = builder.toString(); + queuedCallback = ioCallback; + } + + private void queue(final String data, final IoCallback callback) { + //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely + if (next != null || pendingFile != null) { + throw UndertowMessages.MESSAGES.dataAlreadyQueued(); + } + next = data; + queuedCallback = callback; + } + private void queue(final FileChannel data, final IoCallback callback) { + //if data is sent from withing the callback we queue it, to prevent the stack growing indefinitely + if (next != null || pendingFile != null) { + throw UndertowMessages.MESSAGES.dataAlreadyQueued(); + } + pendingFile = data; + queuedCallback = callback; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/CompositeThreadSetupAction.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/CompositeThreadSetupAction.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/CompositeThreadSetupAction.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,83 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.util.List; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.ThreadSetupAction; + +/** + * @author Stuart Douglas + */ +public class CompositeThreadSetupAction implements ThreadSetupAction { + + private final ThreadSetupAction[] actions; + + public CompositeThreadSetupAction(final List actions) { + this.actions = actions.toArray(new ThreadSetupAction[actions.size()]); + } + + @Override + public Handle setup(final HttpServerExchange exchange) { + final Handle[] handles = new Handle[actions.length]; + try { + for (int i = 0; i < handles.length; ++i) { + handles[handles.length - i - 1] = actions[i].setup(exchange); //add them in reverse order + } + return new Handle() { + @Override + public void tearDown() { + Throwable problem = null; + for (int i = 0; i < handles.length; ++i) { + Handle handle = handles[i]; + if (handle != null) { + try { + handle.tearDown(); + } catch (Throwable e) { + problem = e; + } + } + } + if (problem != null) { + throw new RuntimeException(problem); + } + } + }; + } catch (RuntimeException e) { + for (final Handle handle : handles) { + try { + handle.tearDown(); + } catch (Throwable ignore) { + + } + } + throw e; + } catch (Error e) { + for (final Handle handle : handles) { + try { + handle.tearDown(); + } catch (Throwable ignore) { + + } + } + throw e; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ContextClassLoaderSetupAction.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ContextClassLoaderSetupAction.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ContextClassLoaderSetupAction.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,46 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.ThreadSetupAction; + +/** + * @author Stuart Douglas + */ +public class ContextClassLoaderSetupAction implements ThreadSetupAction { + + private final ClassLoader classLoader; + + public ContextClassLoaderSetupAction(final ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Handle setup(final HttpServerExchange exchange) { + final ClassLoader old = SecurityActions.getContextClassLoader(); + SecurityActions.setContextClassLoader(classLoader); + return new Handle() { + @Override + public void tearDown() { + SecurityActions.setContextClassLoader(old); + } + }; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/DefaultAuthorizationManager.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/DefaultAuthorizationManager.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/DefaultAuthorizationManager.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,92 @@ +package io.undertow.servlet.core; + +import io.undertow.security.idm.Account; +import io.undertow.servlet.api.AuthorizationManager; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.SecurityInfo; +import io.undertow.servlet.api.SecurityRoleRef; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.api.SingleConstraintMatch; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Default authorization manager that simply implements the rules as specified by the servlet spec + * + * @author Stuart Douglas + */ +public class DefaultAuthorizationManager implements AuthorizationManager { + + public static final DefaultAuthorizationManager INSTANCE = new DefaultAuthorizationManager(); + + private DefaultAuthorizationManager() { + } + + @Override + public boolean isUserInRole(String role, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { + + final Map> principalVersusRolesMap = deployment.getDeploymentInfo().getPrincipalVersusRolesMap(); + final Set roles = principalVersusRolesMap.get(account.getPrincipal().getName()); + //TODO: a more efficient imple + for (SecurityRoleRef ref : servletInfo.getSecurityRoleRefs()) { + if (ref.getRole().equals(role)) { + if (roles != null && roles.contains(ref.getLinkedRole())) { + return true; + } + return account.getRoles().contains(ref.getLinkedRole()); + } + } + if (roles != null && roles.contains(role)) { + return true; + } + return account.getRoles().contains(role); + } + + @Override + public boolean canAccessResource(List constraints, Account account, ServletInfo servletInfo, HttpServletRequest request, Deployment deployment) { + if (constraints == null || constraints.isEmpty()) { + return true; + } + for (final SingleConstraintMatch constraint : constraints) { + + boolean found = false; + + Set roleSet = constraint.getRequiredRoles(); + if (roleSet.isEmpty() && constraint.getEmptyRoleSemantic() != SecurityInfo.EmptyRoleSemantic.DENY) { + /* + * The EmptyRoleSemantic was either PERMIT or AUTHENTICATE, either way a roles check is not needed. + */ + found = true; + } else if (account != null) { + final Set roles = deployment.getDeploymentInfo().getPrincipalVersusRolesMap().get(account.getPrincipal().getName()); + + for (String role : roleSet) { + if (roles != null) { + if (roles.contains(role)) { + found = true; + break; + } + } + if (account.getRoles().contains(role)) { + found = true; + break; + } + } + } + if (!found) { + return false; + } + } + return true; + + } + + @Override + public TransportGuaranteeType transportGuarantee(TransportGuaranteeType currentConnectionGuarantee, TransportGuaranteeType configuredRequiredGuarentee, HttpServletRequest request) { + return configuredRequiredGuarentee; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,231 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.server.HttpHandler; +import io.undertow.server.session.SessionManager; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletDispatcher; +import io.undertow.servlet.handlers.ServletInitialHandler; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.servlet.spec.ServletContextImpl; + +/** + * Class that represents the mutable state associated with a servlet deployment that is built up + * during the bootstrap process. + *

+ * Classes calling deployment methods during bootstrap must be aware of ordering concerns. + * + * @author Stuart Douglas + */ +public class DeploymentImpl implements Deployment { + + private final DeploymentManager deploymentManager; + private final DeploymentInfo deploymentInfo; + private final ServletContainer servletContainer; + private final List lifecycleObjects = new ArrayList(); + private final ServletPathMatches servletPaths; + private final ManagedServlets servlets; + private final ManagedFilters filters; + private final Executor executor; + private final Executor asyncExecutor; + + + private volatile ApplicationListeners applicationListeners; + private volatile ServletContextImpl servletContext; + private volatile ServletInitialHandler servletHandler; + private volatile HttpHandler initialHandler; + private volatile CompositeThreadSetupAction threadSetupAction; + private volatile ErrorPages errorPages; + private volatile Map mimeExtensionMappings; + private volatile SessionManager sessionManager; + private volatile Charset defaultCharset; + private volatile List authenticationMechanisms; + + public DeploymentImpl(DeploymentManager deploymentManager, final DeploymentInfo deploymentInfo, ServletContainer servletContainer) { + this.deploymentManager = deploymentManager; + this.deploymentInfo = deploymentInfo; + this.servletContainer = servletContainer; + this.executor = deploymentInfo.getExecutor(); + this.asyncExecutor = deploymentInfo.getAsyncExecutor(); + servletPaths = new ServletPathMatches(this); + servlets = new ManagedServlets(this, servletPaths); + filters = new ManagedFilters(this, servletPaths); + } + + @Override + public ServletContainer getServletContainer() { + return servletContainer; + } + + public ManagedServlets getServlets() { + return servlets; + } + + public ManagedFilters getFilters() { + return filters; + } + + void setApplicationListeners(final ApplicationListeners applicationListeners) { + this.applicationListeners = applicationListeners; + } + + void setServletContext(final ServletContextImpl servletContext) { + this.servletContext = servletContext; + } + + @Override + public DeploymentInfo getDeploymentInfo() { + return deploymentInfo; + } + + @Override + public ApplicationListeners getApplicationListeners() { + return applicationListeners; + } + + @Override + public ServletContextImpl getServletContext() { + return servletContext; + } + + @Override + public HttpHandler getHandler() { + return initialHandler; + } + + public void setInitialHandler(final HttpHandler initialHandler) { + this.initialHandler = initialHandler; + } + + void setServletHandler(final ServletInitialHandler servletHandler) { + this.servletHandler = servletHandler; + } + + void addLifecycleObjects(final Collection objects) { + lifecycleObjects.addAll(objects); + } + + void addLifecycleObjects(final Lifecycle... objects) { + lifecycleObjects.addAll(Arrays.asList(objects)); + } + + void setSessionManager(final SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public List getLifecycleObjects() { + return Collections.unmodifiableList(lifecycleObjects); + } + + @Override + public ServletPathMatches getServletPaths() { + return servletPaths; + } + + public CompositeThreadSetupAction getThreadSetupAction() { + return threadSetupAction; + } + + public void setThreadSetupAction(final CompositeThreadSetupAction threadSetupAction) { + this.threadSetupAction = threadSetupAction; + } + + public ErrorPages getErrorPages() { + return errorPages; + } + + public void setErrorPages(final ErrorPages errorPages) { + this.errorPages = errorPages; + } + + @Override + public Map getMimeExtensionMappings() { + return mimeExtensionMappings; + } + + public void setMimeExtensionMappings(final Map mimeExtensionMappings) { + this.mimeExtensionMappings = Collections.unmodifiableMap(new HashMap(mimeExtensionMappings)); + } + + @Override + public ServletDispatcher getServletDispatcher() { + return servletHandler; + } + + @Override + public SessionManager getSessionManager() { + return sessionManager; + } + + @Override + public Executor getExecutor() { + return executor; + } + + @Override + public Executor getAsyncExecutor() { + return asyncExecutor; + } + + public Charset getDefaultCharset() { + return defaultCharset; + } + + public void setAuthenticationMechanisms(List authenticationMechanisms) { + this.authenticationMechanisms = authenticationMechanisms; + } + + @Override + public List getAuthenticationMechanisms() { + return authenticationMechanisms; + } + + @Override + public DeploymentManager.State getDeploymentState() { + return deploymentManager.getState(); + } + + public void setDefaultCharset(Charset defaultCharset) { + this.defaultCharset = defaultCharset; + } + + void destroy(){ + getApplicationListeners().contextDestroyed(); + getApplicationListeners().stop(); + if (servletContext!=null){ + servletContext.destroy(); + } + servletContext = null; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentManagerImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentManagerImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/DeploymentManagerImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,590 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import io.undertow.Handlers; +import io.undertow.predicate.Predicates; +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.security.api.AuthenticationMechanismFactory; +import io.undertow.security.api.AuthenticationMode; +import io.undertow.security.api.NotificationReceiver; +import io.undertow.security.api.SecurityContextFactory; +import io.undertow.security.handlers.AuthenticationMechanismsHandler; +import io.undertow.security.handlers.NotificationReceiverHandler; +import io.undertow.security.handlers.SecurityInitialHandler; +import io.undertow.security.impl.BasicAuthenticationMechanism; +import io.undertow.security.impl.CachedAuthenticatedSessionMechanism; +import io.undertow.security.impl.ClientCertAuthenticationMechanism; +import io.undertow.security.impl.DigestAuthenticationMechanism; +import io.undertow.security.impl.ExternalAuthenticationMechanism; +import io.undertow.security.impl.SecurityContextFactoryImpl; +import io.undertow.server.HandlerWrapper; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.HttpContinueReadHandler; +import io.undertow.server.handlers.PredicateHandler; +import io.undertow.server.handlers.form.FormEncodedDataDefinition; +import io.undertow.server.handlers.form.FormParserFactory; +import io.undertow.server.session.SessionManager; +import io.undertow.servlet.ServletExtension; +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.AuthMethodConfig; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ErrorPage; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.HttpMethodSecurityInfo; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.LoginConfig; +import io.undertow.servlet.api.MetricsCollector; +import io.undertow.servlet.api.MimeMapping; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletContainerInitializerInfo; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.ServletSecurityInfo; +import io.undertow.servlet.api.ServletSessionConfig; +import io.undertow.servlet.api.ServletStackTraces; +import io.undertow.servlet.api.SessionPersistenceManager; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.api.WebResourceCollection; +import io.undertow.servlet.handlers.ServletDispatchingHandler; +import io.undertow.servlet.handlers.ServletHandler; +import io.undertow.servlet.handlers.ServletInitialHandler; +import io.undertow.servlet.handlers.SessionRestoringHandler; +import io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler; +import io.undertow.servlet.handlers.security.SSLInformationAssociationHandler; +import io.undertow.servlet.handlers.security.SecurityPathMatches; +import io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler; +import io.undertow.servlet.handlers.security.ServletAuthenticationConstraintHandler; +import io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler; +import io.undertow.servlet.handlers.security.ServletFormAuthenticationMechanism; +import io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler; +import io.undertow.servlet.predicate.DispatcherTypePredicate; +import io.undertow.servlet.spec.ServletContextImpl; +import io.undertow.servlet.spec.SessionCookieConfigImpl; +import io.undertow.util.MimeMappings; + +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.SessionTrackingMode; +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TreeMap; + +import static javax.servlet.http.HttpServletRequest.BASIC_AUTH; +import static javax.servlet.http.HttpServletRequest.CLIENT_CERT_AUTH; +import static javax.servlet.http.HttpServletRequest.DIGEST_AUTH; +import static javax.servlet.http.HttpServletRequest.FORM_AUTH; + +/** + * The deployment manager. This manager is responsible for controlling the lifecycle of a servlet deployment. + * + * @author Stuart Douglas + */ +public class DeploymentManagerImpl implements DeploymentManager { + + /** + * The original deployment information, this is + */ + private final DeploymentInfo originalDeployment; + + private final ServletContainer servletContainer; + + /** + * Current deployment, this may be modified by SCI's + */ + private volatile DeploymentImpl deployment; + private volatile State state = State.UNDEPLOYED; + + public DeploymentManagerImpl(final DeploymentInfo deployment, final ServletContainer servletContainer) { + this.originalDeployment = deployment; + this.servletContainer = servletContainer; + } + + @Override + public void deploy() { + DeploymentInfo deploymentInfo = originalDeployment.clone(); + + if (deploymentInfo.getServletStackTraces() == ServletStackTraces.ALL) { + UndertowServletLogger.REQUEST_LOGGER.servletStackTracesAll(deploymentInfo.getDeploymentName()); + } + + deploymentInfo.validate(); + final DeploymentImpl deployment = new DeploymentImpl(this, deploymentInfo, servletContainer); + this.deployment = deployment; + + final ServletContextImpl servletContext = new ServletContextImpl(servletContainer, deployment); + deployment.setServletContext(servletContext); + handleExtensions(deploymentInfo, servletContext); + + deployment.setDefaultCharset(Charset.forName(deploymentInfo.getDefaultEncoding())); + + handleDeploymentSessionConfig(deploymentInfo, servletContext); + + deployment.setSessionManager(deploymentInfo.getSessionManagerFactory().createSessionManager(deployment)); + deployment.getSessionManager().setDefaultSessionTimeout(deploymentInfo.getDefaultSessionTimeout()); + + final List setup = new ArrayList(); + setup.add(new ContextClassLoaderSetupAction(deploymentInfo.getClassLoader())); + setup.addAll(deploymentInfo.getThreadSetupActions()); + final CompositeThreadSetupAction threadSetupAction = new CompositeThreadSetupAction(setup); + deployment.setThreadSetupAction(threadSetupAction); + + ThreadSetupAction.Handle handle = threadSetupAction.setup(null); + try { + + final ApplicationListeners listeners = createListeners(); + listeners.start(); + + deployment.setApplicationListeners(listeners); + + //now create the servlets and filters that we know about. We can still get more later + createServletsAndFilters(deployment, deploymentInfo); + + //first run the SCI's + for (final ServletContainerInitializerInfo sci : deploymentInfo.getServletContainerInitializers()) { + final InstanceHandle instance = sci.getInstanceFactory().createInstance(); + try { + instance.getInstance().onStartup(sci.getHandlesTypes(), servletContext); + } finally { + instance.release(); + } + } + + deployment.getSessionManager().registerSessionListener(new SessionListenerBridge(threadSetupAction, listeners, servletContext)); + + initializeErrorPages(deployment, deploymentInfo); + initializeMimeMappings(deployment, deploymentInfo); + initializeTempDir(servletContext, deploymentInfo); + listeners.contextInitialized(); + //run + + HttpHandler wrappedHandlers = ServletDispatchingHandler.INSTANCE; + wrappedHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getInnerHandlerChainWrappers()); + HttpHandler securityHandler = setupSecurityHandlers(wrappedHandlers); + wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, securityHandler, wrappedHandlers); + + HttpHandler outerHandlers = wrapHandlers(wrappedHandlers, deploymentInfo.getOuterHandlerChainWrappers()); + wrappedHandlers = new PredicateHandler(DispatcherTypePredicate.REQUEST, outerHandlers, wrappedHandlers); + wrappedHandlers = handleDevelopmentModePersistentSessions(wrappedHandlers, deploymentInfo, deployment.getSessionManager(), servletContext); + + MetricsCollector metrics = deploymentInfo.getMetricsCollector(); + if(metrics != null) { + wrappedHandlers = new MetricsChainHandler(wrappedHandlers, metrics, deployment); + } + + final ServletInitialHandler servletInitialHandler = SecurityActions.createServletInitialHandler(deployment.getServletPaths(), wrappedHandlers, deployment.getThreadSetupAction(), servletContext); + + HttpHandler initialHandler = wrapHandlers(servletInitialHandler, deployment.getDeploymentInfo().getInitialHandlerChainWrappers()); + initialHandler = new HttpContinueReadHandler(initialHandler); + if(deploymentInfo.getUrlEncoding() != null) { + initialHandler = Handlers.urlDecodingHandler(deploymentInfo.getUrlEncoding(), initialHandler); + } + deployment.setInitialHandler(initialHandler); + deployment.setServletHandler(servletInitialHandler); + deployment.getServletPaths().invalidate(); //make sure we have a fresh set of servlet paths + servletContext.initDone(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + handle.tearDown(); + } + state = State.DEPLOYED; + } + + private void createServletsAndFilters(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { + for (Map.Entry servlet : deploymentInfo.getServlets().entrySet()) { + deployment.getServlets().addServlet(servlet.getValue()); + } + for (Map.Entry filter : deploymentInfo.getFilters().entrySet()) { + deployment.getFilters().addFilter(filter.getValue()); + } + } + + private void handleExtensions(final DeploymentInfo deploymentInfo, final ServletContextImpl servletContext) { + Set> loadedExtensions = new HashSet>(); + + for (ServletExtension extension : ServiceLoader.load(ServletExtension.class, deploymentInfo.getClassLoader())) { + loadedExtensions.add(extension.getClass()); + extension.handleDeployment(deploymentInfo, servletContext); + } + + if (!ServletExtension.class.getClassLoader().equals(deploymentInfo.getClassLoader())) { + for (ServletExtension extension : ServiceLoader.load(ServletExtension.class)) { + + // Note: If the CLs are different, but can the see the same extensions and extension might get loaded + // and thus instantiated twice, but the handleDeployment() is executed only once. + + if (!loadedExtensions.contains(extension.getClass())) { + extension.handleDeployment(deploymentInfo, servletContext); + } + } + } + for(ServletExtension extension : deploymentInfo.getServletExtensions()) { + extension.handleDeployment(deploymentInfo, servletContext); + } + } + + /** + * sets up the outer security handlers. + *

+ * the handler that actually performs the access check happens later in the chain, it is not setup here + * + * @param initialHandler The handler to wrap with security handlers + */ + private HttpHandler setupSecurityHandlers(HttpHandler initialHandler) { + final DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + final LoginConfig loginConfig = deploymentInfo.getLoginConfig(); + + final Map factoryMap = new HashMap(deploymentInfo.getAuthenticationMechanisms()); + if(!factoryMap.containsKey(BASIC_AUTH)) { + factoryMap.put(BASIC_AUTH, BasicAuthenticationMechanism.FACTORY); + } + if(!factoryMap.containsKey(FORM_AUTH)) { + factoryMap.put(FORM_AUTH, ServletFormAuthenticationMechanism.FACTORY); + } + if(!factoryMap.containsKey(DIGEST_AUTH)) { + factoryMap.put(DIGEST_AUTH, DigestAuthenticationMechanism.FACTORY); + } + if(!factoryMap.containsKey(CLIENT_CERT_AUTH)) { + factoryMap.put(CLIENT_CERT_AUTH, ClientCertAuthenticationMechanism.FACTORY); + } + if(!factoryMap.containsKey(ExternalAuthenticationMechanism.NAME)) { + factoryMap.put(ExternalAuthenticationMechanism.NAME, ExternalAuthenticationMechanism.FACTORY); + } + HttpHandler current = initialHandler; + current = new SSLInformationAssociationHandler(current); + + final SecurityPathMatches securityPathMatches = buildSecurityConstraints(); + current = new ServletAuthenticationCallHandler(current); + if(deploymentInfo.isDisableCachingForSecuredPages()) { + current = Handlers.predicate(Predicates.authRequired(), Handlers.disableCache(current), current); + } + if (!securityPathMatches.isEmpty()) { + current = new ServletAuthenticationConstraintHandler(current); + } + current = new ServletConfidentialityConstraintHandler(deploymentInfo.getConfidentialPortManager(), current); + if (!securityPathMatches.isEmpty()) { + current = new ServletSecurityConstraintHandler(securityPathMatches, current); + } + List authenticationMechanisms = new LinkedList(); + authenticationMechanisms.add(new CachedAuthenticatedSessionMechanism()); //TODO: does this really need to be hard coded? + + String mechName = null; + if (loginConfig != null || deploymentInfo.getJaspiAuthenticationMechanism() != null) { + + //we don't allow multipart requests, and always use the default encoding + FormParserFactory parser = FormParserFactory.builder(false) + .addParser(new FormEncodedDataDefinition().setDefaultEncoding(deploymentInfo.getDefaultEncoding())) + .build(); + + List authMethods = Collections.emptyList(); + if(loginConfig != null) { + authMethods = loginConfig.getAuthMethods(); + } + + for(AuthMethodConfig method : authMethods) { + AuthenticationMechanismFactory factory = factoryMap.get(method.getName()); + if(factory == null) { + throw UndertowServletMessages.MESSAGES.unknownAuthenticationMechanism(method.getName()); + } + if(mechName == null) { + mechName = method.getName(); + } + + final Map properties = new HashMap(); + properties.put(AuthenticationMechanismFactory.CONTEXT_PATH, deploymentInfo.getContextPath()); + properties.put(AuthenticationMechanismFactory.REALM, loginConfig.getRealmName()); + properties.put(AuthenticationMechanismFactory.ERROR_PAGE, loginConfig.getErrorPage()); + properties.put(AuthenticationMechanismFactory.LOGIN_PAGE, loginConfig.getLoginPage()); + properties.putAll(method.getProperties()); + + String name = method.getName().toUpperCase(Locale.US); + // The mechanism name is passed in from the HttpServletRequest interface as the name reported needs to be + // comparable using '==' + name = name.equals(FORM_AUTH) ? FORM_AUTH : name; + name = name.equals(BASIC_AUTH) ? BASIC_AUTH : name; + name = name.equals(DIGEST_AUTH) ? DIGEST_AUTH : name; + name = name.equals(CLIENT_CERT_AUTH) ? CLIENT_CERT_AUTH : name; + + authenticationMechanisms.add(factory.create(name, parser, properties)); + } + } + + deployment.setAuthenticationMechanisms(authenticationMechanisms); + //if the JASPI auth mechanism is set then it takes over + if(deploymentInfo.getJaspiAuthenticationMechanism() == null) { + current = new AuthenticationMechanismsHandler(current, authenticationMechanisms); + } else { + current = new AuthenticationMechanismsHandler(current, Collections.singletonList(deploymentInfo.getJaspiAuthenticationMechanism())); + } + + current = new CachedAuthenticatedSessionHandler(current, this.deployment.getServletContext()); + List notificationReceivers = deploymentInfo.getNotificationReceivers(); + if (!notificationReceivers.isEmpty()) { + current = new NotificationReceiverHandler(current, notificationReceivers); + } + + // TODO - A switch to constraint driven could be configurable, however before we can support that with servlets we would + // need additional tracking within sessions if a servlet has specifically requested that authentication occurs. + SecurityContextFactory contextFactory = deploymentInfo.getSecurityContextFactory(); + if (contextFactory == null) { + contextFactory = SecurityContextFactoryImpl.INSTANCE; + } + current = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, deploymentInfo.getIdentityManager(), mechName, + contextFactory, current); + return current; + } + + private SecurityPathMatches buildSecurityConstraints() { + SecurityPathMatches.Builder builder = SecurityPathMatches.builder(deployment.getDeploymentInfo()); + final Set urlPatterns = new HashSet(); + for (SecurityConstraint constraint : deployment.getDeploymentInfo().getSecurityConstraints()) { + builder.addSecurityConstraint(constraint); + for (WebResourceCollection webResources : constraint.getWebResourceCollections()) { + urlPatterns.addAll(webResources.getUrlPatterns()); + } + } + + for (final ServletInfo servlet : deployment.getDeploymentInfo().getServlets().values()) { + final ServletSecurityInfo securityInfo = servlet.getServletSecurityInfo(); + if (securityInfo != null) { + final Set mappings = new HashSet(servlet.getMappings()); + mappings.removeAll(urlPatterns); + if (!mappings.isEmpty()) { + final Set methods = new HashSet(); + + for (HttpMethodSecurityInfo method : securityInfo.getHttpMethodSecurityInfo()) { + methods.add(method.getMethod()); + if (method.getRolesAllowed().isEmpty() && method.getEmptyRoleSemantic() == EmptyRoleSemantic.PERMIT) { + //this is an implict allow + continue; + } + SecurityConstraint newConstraint = new SecurityConstraint() + .addRolesAllowed(method.getRolesAllowed()) + .setTransportGuaranteeType(method.getTransportGuaranteeType()) + .addWebResourceCollection(new WebResourceCollection().addUrlPatterns(mappings) + .addHttpMethod(method.getMethod())); + builder.addSecurityConstraint(newConstraint); + } + //now add the constraint, unless it has all default values and method constrains where specified + if (!securityInfo.getRolesAllowed().isEmpty() + || securityInfo.getEmptyRoleSemantic() != EmptyRoleSemantic.PERMIT + || methods.isEmpty()) { + SecurityConstraint newConstraint = new SecurityConstraint() + .setEmptyRoleSemantic(securityInfo.getEmptyRoleSemantic()) + .addRolesAllowed(securityInfo.getRolesAllowed()) + .setTransportGuaranteeType(securityInfo.getTransportGuaranteeType()) + .addWebResourceCollection(new WebResourceCollection().addUrlPatterns(mappings) + .addHttpMethodOmissions(methods)); + builder.addSecurityConstraint(newConstraint); + } + } + + } + } + + return builder.build(); + } + + private void initializeTempDir(final ServletContextImpl servletContext, final DeploymentInfo deploymentInfo) { + if (deploymentInfo.getTempDir() != null) { + servletContext.setAttribute(ServletContext.TEMPDIR, deploymentInfo.getTempDir()); + } else { + servletContext.setAttribute(ServletContext.TEMPDIR, new File(SecurityActions.getSystemProperty("java.io.tmpdir"))); + } + } + + private void initializeMimeMappings(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { + final Map mappings = new HashMap(MimeMappings.DEFAULT_MIME_MAPPINGS); + for (MimeMapping mapping : deploymentInfo.getMimeMappings()) { + mappings.put(mapping.getExtension(), mapping.getMimeType()); + } + deployment.setMimeExtensionMappings(mappings); + } + + private void initializeErrorPages(final DeploymentImpl deployment, final DeploymentInfo deploymentInfo) { + final Map codes = new HashMap(); + final Map, String> exceptions = new HashMap, String>(); + String defaultErrorPage = null; + for (final ErrorPage page : deploymentInfo.getErrorPages()) { + if (page.getExceptionType() != null) { + exceptions.put(page.getExceptionType(), page.getLocation()); + } else if (page.getErrorCode() != null) { + codes.put(page.getErrorCode(), page.getLocation()); + } else { + if (defaultErrorPage != null) { + throw UndertowServletMessages.MESSAGES.moreThanOneDefaultErrorPage(defaultErrorPage, page.getLocation()); + } else { + defaultErrorPage = page.getLocation(); + } + } + } + deployment.setErrorPages(new ErrorPages(codes, exceptions, defaultErrorPage)); + } + + + private ApplicationListeners createListeners() { + final List managedListeners = new ArrayList(); + for (final ListenerInfo listener : deployment.getDeploymentInfo().getListeners()) { + managedListeners.add(new ManagedListener(listener, false)); + } + return new ApplicationListeners(managedListeners, deployment.getServletContext()); + } + + + private static HttpHandler wrapHandlers(final HttpHandler wrapee, final List wrappers) { + HttpHandler current = wrapee; + for (HandlerWrapper wrapper : wrappers) { + current = wrapper.wrap(current); + } + return current; + } + + @Override + public HttpHandler start() throws ServletException { + ThreadSetupAction.Handle handle = deployment.getThreadSetupAction().setup(null); + try { + deployment.getSessionManager().start(); + + //we need to copy before iterating + //because listeners can add other listeners + ArrayList lifecycles = new ArrayList(deployment.getLifecycleObjects()); + for (Lifecycle object : lifecycles) { + object.start(); + } + HttpHandler root = deployment.getHandler(); + final TreeMap> loadOnStartup = new TreeMap>(); + for(Map.Entry entry: deployment.getServlets().getServletHandlers().entrySet()) { + ManagedServlet servlet = entry.getValue().getManagedServlet(); + Integer loadOnStartupNumber = servlet.getServletInfo().getLoadOnStartup(); + if(loadOnStartupNumber != null) { + if(loadOnStartupNumber < 0) { + continue; + } + List list = loadOnStartup.get(loadOnStartupNumber); + if(list == null) { + loadOnStartup.put(loadOnStartupNumber, list = new ArrayList()); + } + list.add(servlet); + } + } + for(Map.Entry> load : loadOnStartup.entrySet()) { + for(ManagedServlet servlet : load.getValue()) { + servlet.createServlet(); + } + } + + if (deployment.getDeploymentInfo().isEagerFilterInit()){ + for(ManagedFilter filter: deployment.getFilters().getFilters().values()) { + filter.createFilter(); + } + } + + state = State.STARTED; + return root; + } finally { + handle.tearDown(); + } + } + + @Override + public void stop() throws ServletException { + ThreadSetupAction.Handle handle = deployment.getThreadSetupAction().setup(null); + try { + for (Lifecycle object : deployment.getLifecycleObjects()) { + object.stop(); + } + deployment.getSessionManager().stop(); + } finally { + handle.tearDown(); + } + state = State.DEPLOYED; + } + + private HttpHandler handleDevelopmentModePersistentSessions(HttpHandler next, final DeploymentInfo deploymentInfo, final SessionManager sessionManager, final ServletContextImpl servletContext) { + final SessionPersistenceManager sessionPersistenceManager = deploymentInfo.getSessionPersistenceManager(); + if (sessionPersistenceManager != null) { + SessionRestoringHandler handler = new SessionRestoringHandler(deployment.getDeploymentInfo().getDeploymentName(), sessionManager, servletContext, next, sessionPersistenceManager); + deployment.addLifecycleObjects(handler); + return handler; + } + return next; + } + + public void handleDeploymentSessionConfig(DeploymentInfo deploymentInfo, ServletContextImpl servletContext) { + SessionCookieConfigImpl sessionCookieConfig = servletContext.getSessionCookieConfig(); + ServletSessionConfig sc = deploymentInfo.getServletSessionConfig(); + if (sc != null) { + sessionCookieConfig.setName(sc.getName()); + sessionCookieConfig.setComment(sc.getComment()); + sessionCookieConfig.setDomain(sc.getDomain()); + sessionCookieConfig.setHttpOnly(sc.isHttpOnly()); + sessionCookieConfig.setMaxAge(sc.getMaxAge()); + if(sc.getPath() != null) { + sessionCookieConfig.setPath(sc.getPath()); + } else { + sessionCookieConfig.setPath(deploymentInfo.getContextPath()); + } + sessionCookieConfig.setSecure(sc.isSecure()); + if (sc.getSessionTrackingModes() != null) { + servletContext.setDefaultSessionTrackingModes(new HashSet(sc.getSessionTrackingModes())); + } + } + } + + + @Override + public void undeploy() { + ThreadSetupAction.Handle handle = deployment.getThreadSetupAction().setup(null); + try { + deployment.destroy(); + deployment = null; + } finally { + handle.tearDown(); + } + state = State.UNDEPLOYED; + } + + @Override + public State getState() { + return state; + } + + @Override + public Deployment getDeployment() { + return deployment; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ErrorPages.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ErrorPages.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ErrorPages.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,74 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.util.Map; + +import javax.servlet.ServletException; + +/** + * Class that maintains information about error page mappings. + * + * @author Stuart Douglas + */ +public class ErrorPages { + + private final Map errorCodeLocations; + private final Map, String> exceptionMappings; + private final String defaultErrorPage; + + public ErrorPages(final Map errorCodeLocations, final Map, String> exceptionMappings, final String defaultErrorPage) { + this.errorCodeLocations = errorCodeLocations; + this.exceptionMappings = exceptionMappings; + this.defaultErrorPage = defaultErrorPage; + } + + public String getErrorLocation(final int code) { + String location = errorCodeLocations.get(code); + if (location == null) { + return defaultErrorPage; + } + return location; + } + + public String getErrorLocation(final Throwable exception) { + if (exception == null) { + return null; + } + //todo: this is kinda slow, but there is probably not a great deal that can be done about it + String location = null; + for (Class c = exception.getClass(); c != null && location == null; c = c.getSuperclass()) { + location = exceptionMappings.get(c); + } + if (location == null && exception instanceof ServletException) { + Throwable rootCause = ((ServletException) exception).getRootCause(); + if (rootCause != null) { + for (Class c = rootCause.getClass(); c != null && location == null; c = c.getSuperclass()) { + location = exceptionMappings.get(c); + } + } + } + if (location == null) { + location = defaultErrorPage; + } + return location; + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/InMemorySessionManagerFactory.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/InMemorySessionManagerFactory.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/InMemorySessionManagerFactory.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,46 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import io.undertow.server.session.InMemorySessionManager; +import io.undertow.server.session.SessionManager; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.SessionManagerFactory; + +/** + * Session manager factory that creates an in-memory session manager + * @author Paul Ferraro + */ +public class InMemorySessionManagerFactory implements SessionManagerFactory { + + private final int maxSessions; + + public InMemorySessionManagerFactory() { + this(-1); + } + + public InMemorySessionManagerFactory(int maxSessions) { + this.maxSessions = maxSessions; + } + + @Override + public SessionManager createSessionManager(Deployment deployment) { + return new InMemorySessionManager(deployment.getDeploymentInfo().getDeploymentName(), maxSessions); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/Lifecycle.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/Lifecycle.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/Lifecycle.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,37 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import javax.servlet.ServletException; + +/** + * + * An object that can be started or stopped. + * + * @author Stuart Douglas + */ +public interface Lifecycle { + + void start() throws ServletException; + + void stop() throws ServletException; + + boolean isStarted(); + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilter.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilter.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilter.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,110 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.spec.FilterConfigImpl; +import io.undertow.servlet.spec.ServletContextImpl; + +/** + * @author Stuart Douglas + */ +public class ManagedFilter implements Lifecycle { + + private final FilterInfo filterInfo; + private final ServletContextImpl servletContext; + + private volatile boolean started = false; + private volatile Filter filter; + private volatile InstanceHandle handle; + + public ManagedFilter(final FilterInfo filterInfo, final ServletContextImpl servletContext) { + this.filterInfo = filterInfo; + this.servletContext = servletContext; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) { + throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName()); + } + if (!started) { + start(); + } + getFilter().doFilter(request, response, chain); + } + + private Filter getFilter() throws ServletException { + if (filter == null) { + createFilter(); + } + return filter; + } + + public void createFilter() throws ServletException { + synchronized (this) { + if (filter == null) { + try { + handle = filterInfo.getInstanceFactory().createInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(filterInfo.getName(), e); + } + Filter filter = handle.getInstance(); + filter.init(new FilterConfigImpl(filterInfo, servletContext)); + this.filter = filter; + } + } + } + + public synchronized void start() throws ServletException { + if (!started) { + + started = true; + } + } + + public synchronized void stop() { + started = false; + if (handle != null) { + filter.destroy(); + handle.release(); + } + filter = null; + handle = null; + } + + @Override + public boolean isStarted() { + return started; + } + + public FilterInfo getFilterInfo() { + return filterInfo; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilters.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilters.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedFilters.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,42 @@ +package io.undertow.servlet.core; + +import java.util.HashMap; +import java.util.Map; + +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.util.CopyOnWriteMap; + +/** + * Runtime representation of filters. Basically a container for {@link io.undertow.servlet.core.ManagedFilter} instances + * + * @author Stuart Douglas + */ +public class ManagedFilters { + + private final Map managedFilterMap = new CopyOnWriteMap(); + private final DeploymentImpl deployment; + private final ServletPathMatches servletPathMatches; + + public ManagedFilters(final DeploymentImpl deployment, final ServletPathMatches servletPathMatches) { + this.deployment = deployment; + this.servletPathMatches = servletPathMatches; + } + + public ManagedFilter addFilter(final FilterInfo filterInfo) { + ManagedFilter managedFilter = new ManagedFilter(filterInfo, deployment.getServletContext()); + managedFilterMap.put(filterInfo.getName(),managedFilter); + deployment.addLifecycleObjects(managedFilter); + servletPathMatches.invalidate(); + return managedFilter; + } + + public ManagedFilter getManagedFilter(final String name) { + return managedFilterMap.get(name); + } + + public Map getFilters() { + return new HashMap(managedFilterMap); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedListener.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedListener.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedListener.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,86 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.util.EventListener; + +import javax.servlet.ServletException; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ListenerInfo; + +/** + * @author Stuart Douglas + */ +public class ManagedListener implements Lifecycle { + + private final ListenerInfo listenerInfo; + private final boolean programatic; + + private volatile boolean started = false; + private volatile InstanceHandle handle; + + public ManagedListener(final ListenerInfo listenerInfo, final boolean programatic) { + this.listenerInfo = listenerInfo; + this.programatic = programatic; + } + + public synchronized void start() throws ServletException { + if (!started) { + try { + handle = listenerInfo.getInstanceFactory().createInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(listenerInfo.getListenerClass().getName(), e); + } + started = true; + } + } + + public synchronized void stop() { + started = false; + if (handle != null) { + handle.release(); + } + } + + public ListenerInfo getListenerInfo() { + return listenerInfo; + } + + @Override + public boolean isStarted() { + return started; + } + + public EventListener instance() { + if (!started) { + try { + start(); + } catch (ServletException e) { + throw new RuntimeException(e); + } + } + return handle.getInstance(); + } + + public boolean isProgramatic() { + return programatic; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlet.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlet.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlet.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,304 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.io.File; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.SingleThreadModel; +import javax.servlet.UnavailableException; + +import io.undertow.server.handlers.form.FormEncodedDataDefinition; +import io.undertow.server.handlers.form.FormParserFactory; +import io.undertow.server.handlers.form.MultiPartParserDefinition; +import io.undertow.server.handlers.resource.ResourceChangeListener; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.spec.ServletConfigImpl; +import io.undertow.servlet.spec.ServletContextImpl; + +/** + * Manager for a servlets lifecycle. + * + * @author Stuart Douglas + */ +public class ManagedServlet implements Lifecycle { + + private final ServletInfo servletInfo; + private final ServletContextImpl servletContext; + + private volatile boolean started = false; + private final InstanceStrategy instanceStrategy; + private volatile boolean permanentlyUnavailable = false; + + private final long maxRequestSize; + private final FormParserFactory formParserFactory; + + public ManagedServlet(final ServletInfo servletInfo, final ServletContextImpl servletContext) { + this.servletInfo = servletInfo; + this.servletContext = servletContext; + if (SingleThreadModel.class.isAssignableFrom(servletInfo.getServletClass())) { + instanceStrategy = new SingleThreadModelPoolStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext); + } else { + instanceStrategy = new DefaultInstanceStrategy(servletInfo.getInstanceFactory(), servletInfo, servletContext); + } + FormEncodedDataDefinition formDataParser = new FormEncodedDataDefinition() + .setDefaultEncoding(servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding()); + if (servletInfo.getMultipartConfig() != null) { + //todo: fileSizeThreshold + MultipartConfigElement config = servletInfo.getMultipartConfig(); + if (config.getMaxRequestSize() != -1) { + maxRequestSize = config.getMaxRequestSize(); + } else { + maxRequestSize = -1; + } + final File tempDir; + if(config.getLocation() == null || config.getLocation().isEmpty()) { + tempDir = servletContext.getDeployment().getDeploymentInfo().getTempDir(); + } else { + String location = config.getLocation(); + File locFile = new File(location); + if(locFile.isAbsolute()) { + tempDir = locFile; + } else { + tempDir = new File(servletContext.getDeployment().getDeploymentInfo().getTempDir(), location); + } + } + + MultiPartParserDefinition multiPartParserDefinition = new MultiPartParserDefinition(tempDir); + if(config.getMaxFileSize() > 0) { + multiPartParserDefinition.setMaxIndividualFileSize(config.getMaxFileSize()); + } + multiPartParserDefinition.setDefaultEncoding(servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding()); + + formParserFactory = FormParserFactory.builder(false) + .addParser(formDataParser) + .addParser(multiPartParserDefinition) + .build(); + + } else { + //no multipart config we don't allow multipart requests + formParserFactory = FormParserFactory.builder(false).addParser(formDataParser).build(); + maxRequestSize = -1; + } + } + + + public synchronized void start() throws ServletException { + + } + + public void createServlet() throws ServletException { + if (permanentlyUnavailable) { + return; + } + try { + if (!started && servletInfo.getLoadOnStartup() != null && servletInfo.getLoadOnStartup() >= 0) { + instanceStrategy.start(); + started = true; + } + } catch (UnavailableException e) { + if (e.isPermanent()) { + permanentlyUnavailable = true; + stop(); + } + } + } + + public synchronized void stop() { + if (started) { + instanceStrategy.stop(); + } + started = false; + } + + @Override + public boolean isStarted() { + return started; + } + + public boolean isPermanentlyUnavailable() { + return permanentlyUnavailable; + } + + public void setPermanentlyUnavailable(final boolean permanentlyUnavailable) { + this.permanentlyUnavailable = permanentlyUnavailable; + } + + public InstanceHandle getServlet() throws ServletException { + if(servletContext.getDeployment().getDeploymentState() != DeploymentManager.State.STARTED) { + throw UndertowServletMessages.MESSAGES.deploymentStopped(servletContext.getDeployment().getDeploymentInfo().getDeploymentName()); + } + if (!started) { + synchronized (this) { + if (!started) { + instanceStrategy.start(); + started = true; + } + } + } + return instanceStrategy.getServlet(); + } + + public ServletInfo getServletInfo() { + return servletInfo; + } + + public long getMaxRequestSize() { + return maxRequestSize; + } + + public FormParserFactory getFormParserFactory() { + return formParserFactory; + } + + /** + * interface used to abstract the difference between single thread model servlets and normal servlets + */ + interface InstanceStrategy { + void start() throws ServletException; + + void stop(); + + InstanceHandle getServlet() throws ServletException; + } + + + /** + * The default servlet pooling strategy that just uses a single instance for all requests + */ + private static class DefaultInstanceStrategy implements InstanceStrategy { + + private final InstanceFactory factory; + private final ServletInfo servletInfo; + private final ServletContextImpl servletContext; + private volatile InstanceHandle handle; + private volatile Servlet instance; + private ResourceChangeListener changeListener; + + DefaultInstanceStrategy(final InstanceFactory factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) { + this.factory = factory; + this.servletInfo = servletInfo; + this.servletContext = servletContext; + } + + public synchronized void start() throws ServletException { + try { + handle = factory.createInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e); + } + instance = handle.getInstance(); + instance.init(new ServletConfigImpl(servletInfo, servletContext)); + //if a servlet implements FileChangeCallback it will be notified of file change events + final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager(); + if(instance instanceof ResourceChangeListener && resourceManager.isResourceChangeListenerSupported()) { + resourceManager.registerResourceChangeListener(changeListener = (ResourceChangeListener) instance); + } + } + + public synchronized void stop() { + if (handle != null) { + final ResourceManager resourceManager = servletContext.getDeployment().getDeploymentInfo().getResourceManager(); + if(changeListener != null) { + resourceManager.removeResourceChangeListener(changeListener); + } + instance.destroy(); + handle.release(); + } + } + + public InstanceHandle getServlet() { + return new InstanceHandle() { + @Override + public Servlet getInstance() { + return instance; + } + + @Override + public void release() { + + } + }; + } + } + + /** + * pooling strategy for single thread model servlet + */ + private static class SingleThreadModelPoolStrategy implements InstanceStrategy { + + + private final InstanceFactory factory; + private final ServletInfo servletInfo; + private final ServletContextImpl servletContext; + + private SingleThreadModelPoolStrategy(final InstanceFactory factory, final ServletInfo servletInfo, final ServletContextImpl servletContext) { + this.factory = factory; + this.servletInfo = servletInfo; + this.servletContext = servletContext; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public InstanceHandle getServlet() throws ServletException { + final InstanceHandle instanceHandle; + final Servlet instance; + //TODO: pooling + try { + instanceHandle = factory.createInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(servletInfo.getName(), e); + } + instance = instanceHandle.getInstance(); + + instance.init(new ServletConfigImpl(servletInfo, servletContext)); + return new InstanceHandle() { + @Override + public Servlet getInstance() { + return instance; + } + + @Override + public void release() { + instance.destroy(); + instanceHandle.release(); + } + }; + + } + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlets.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlets.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ManagedServlets.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,53 @@ +package io.undertow.servlet.core; + +import java.util.HashMap; +import java.util.Map; + +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.handlers.ServletHandler; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.util.CopyOnWriteMap; + +/** + * Runtime representation of servlets. Basically a container for {@link ManagedServlet} instances + * + * @author Stuart Douglas + */ +public class ManagedServlets { + + private final Map managedServletMap = new CopyOnWriteMap(); + private final DeploymentImpl deployment; + private final ServletPathMatches servletPaths; + + public ManagedServlets(final DeploymentImpl deployment, final ServletPathMatches servletPaths) { + this.deployment = deployment; + this.servletPaths = servletPaths; + } + + public ServletHandler addServlet(final ServletInfo servletInfo) { + ManagedServlet managedServlet = new ManagedServlet(servletInfo, deployment.getServletContext()); + ServletHandler servletHandler = new ServletHandler(managedServlet); + managedServletMap.put(servletInfo.getName(), servletHandler); + deployment.addLifecycleObjects(managedServlet); + this.servletPaths.invalidate(); + + return servletHandler; + } + + public ManagedServlet getManagedServlet(final String name) { + ServletHandler servletHandler = managedServletMap.get(name); + if(servletHandler == null) { + return null; + } + return servletHandler.getManagedServlet(); + } + + public ServletHandler getServletHandler(final String name) { + return managedServletMap.get(name); + } + + public Map getServletHandlers() { + return new HashMap(managedServletMap); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/MetricsChainHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/MetricsChainHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/MetricsChainHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,46 @@ +package io.undertow.servlet.core; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.MetricsHandler; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.MetricsCollector; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.handlers.ServletHandler; +import io.undertow.servlet.handlers.ServletRequestContext; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Tomaz Cerar (c) 2014 Red Hat Inc. + */ +class MetricsChainHandler implements HttpHandler { + + + private final HttpHandler next; + private final Map servletHandlers; + + public MetricsChainHandler(HttpHandler next, MetricsCollector collector, Deployment deployment) { + this.next = next; + final Map servletHandlers = new HashMap(); + for(Map.Entry entry : deployment.getServlets().getServletHandlers().entrySet()) { + MetricsHandler handler = new MetricsHandler(next); + servletHandlers.put(entry.getKey(), handler); + collector.registerMetric(entry.getKey(), handler); + } + this.servletHandlers = Collections.unmodifiableMap(servletHandlers); + } + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + ServletInfo servletInfo = context.getCurrentServlet().getManagedServlet().getServletInfo(); + MetricsHandler handler = servletHandlers.get(servletInfo.getName()); + if(handler != null) { + handler.handleRequest(exchange); + } else { + next.handleRequest(exchange); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/SecurityActions.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/SecurityActions.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/SecurityActions.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,140 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.ServletContext; + +import io.undertow.server.HttpHandler; +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletInitialHandler; +import io.undertow.servlet.handlers.ServletPathMatches; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.servlet.spec.ServletContextImpl; + +final class SecurityActions { + + private SecurityActions() { + // forbidden inheritance + } + + /** + * Gets context classloader. + * + * @return the current context classloader + */ + static ClassLoader getContextClassLoader() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + } + + /** + * Sets context classloader. + * + * @param classLoader + * the classloader + */ + static void setContextClassLoader(final ClassLoader classLoader) { + if (System.getSecurityManager() == null) { + Thread.currentThread().setContextClassLoader(classLoader); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + Thread.currentThread().setContextClassLoader(classLoader); + return null; + } + }); + } + } + static void setSystemProperty(final String prop, final String value) { + if (System.getSecurityManager() == null) { + System.setProperty(prop, value); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + System.setProperty(prop, value); + return null; + } + }); + } + } + + + static String getSystemProperty(final String prop) { + if (System.getSecurityManager() == null) { + return System.getProperty(prop); + } else { + return (String) AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return System.getProperty(prop); + } + }); + } + } + + static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { + if (System.getSecurityManager() == null) { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public HttpSessionImpl run() { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } + }); + } + } + + static ServletRequestContext currentServletRequestContext() { + if (System.getSecurityManager() == null) { + return ServletRequestContext.current(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletRequestContext run() { + return ServletRequestContext.current(); + } + }); + } + } + + static ServletInitialHandler createServletInitialHandler(final ServletPathMatches paths, final HttpHandler next, final CompositeThreadSetupAction setupAction, final ServletContextImpl servletContext) { + if (System.getSecurityManager() == null) { + return new ServletInitialHandler(paths, next, setupAction, servletContext); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletInitialHandler run() { + return new ServletInitialHandler(paths, next, setupAction, servletContext); + } + }); + } + } +} \ No newline at end of file Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ServletBlockingHttpExchange.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ServletBlockingHttpExchange.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ServletBlockingHttpExchange.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,76 @@ +package io.undertow.servlet.core; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import io.undertow.io.BlockingSenderImpl; +import io.undertow.io.Sender; +import io.undertow.server.BlockingHttpExchange; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpServletRequestImpl; +import io.undertow.servlet.spec.HttpServletResponseImpl; + +/** + * @author Stuart Douglas + */ +public class ServletBlockingHttpExchange implements BlockingHttpExchange { + + private final HttpServerExchange exchange; + + public ServletBlockingHttpExchange(final HttpServerExchange exchange) { + this.exchange = exchange; + } + + @Override + public InputStream getInputStream() { + ServletRequest request = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); + try { + return request.getInputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public OutputStream getOutputStream() { + ServletResponse response = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletResponse(); + try { + return response.getOutputStream(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public Sender getSender() { + try { + return new BlockingSenderImpl(exchange, getOutputStream()); + } catch (IllegalStateException e) { + ServletResponse response = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletResponse(); + try { + return new BlockingWriterSenderImpl(exchange, response.getWriter(), response.getCharacterEncoding()); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + } + } + + @Override + public void close() throws IOException { + if (!exchange.isComplete()) { + ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + try { + HttpServletRequestImpl request = servletRequestContext.getOriginalRequest(); + request.closeAndDrainRequest(); + } finally { + HttpServletResponseImpl response = servletRequestContext.getOriginalResponse(); + response.closeStreamAndWriter(); + } + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ServletContainerImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ServletContainerImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ServletContainerImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,93 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ServletContainer; + +/** + * The manager for all servlet deployments. + * + * @author Stuart Douglas + */ +public class ServletContainerImpl implements ServletContainer { + + + private final Map deployments = Collections.synchronizedMap(new HashMap()); + private final Map deploymentsByPath = Collections.synchronizedMap(new HashMap()); + + @Override + public Collection listDeployments() { + return new HashSet(deployments.keySet()); + } + + @Override + public DeploymentManager addDeployment(final DeploymentInfo deployment) { + final DeploymentInfo dep = deployment.clone(); + DeploymentManager deploymentManager = new DeploymentManagerImpl(dep, this); + deployments.put(dep.getDeploymentName(), deploymentManager); + deploymentsByPath.put(dep.getContextPath(), deploymentManager); + return deploymentManager; + } + + @Override + public DeploymentManager getDeployment(final String deploymentName) { + return deployments.get(deploymentName); + } + + @Override + public void removeDeployment(final DeploymentInfo deploymentInfo) { + final DeploymentManager deploymentManager = deployments.get(deploymentInfo.getDeploymentName()); + if (deploymentManager.getState() != DeploymentManager.State.UNDEPLOYED) { + throw UndertowServletMessages.MESSAGES.canOnlyRemoveDeploymentsWhenUndeployed(deploymentManager.getState()); + } + deployments.remove(deploymentInfo.getDeploymentName()); + deploymentsByPath.remove(deploymentInfo.getContextPath()); + } + + @Override + public DeploymentManager getDeploymentByPath(final String path) { + DeploymentManager exact = deploymentsByPath.get(path); + if (exact != null) { + return exact; + } + int length = path.length(); + int pos = length; + + while (pos > 1) { + --pos; + if (path.charAt(pos) == '/') { + String part = path.substring(0, pos); + DeploymentManager deployment = deploymentsByPath.get(part); + if (deployment != null) { + return deployment; + } + } + } + return deploymentsByPath.get(""); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/ServletUpgradeListener.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/ServletUpgradeListener.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/ServletUpgradeListener.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,104 @@ +package io.undertow.servlet.core; + +import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpUpgradeListener; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.spec.WebConnectionImpl; +import org.xnio.ChannelListener; +import org.xnio.StreamConnection; + +import javax.servlet.http.HttpUpgradeHandler; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Lister that handles a servlet exchange upgrade event. + * + * @author Stuart Douglas + */ +public class ServletUpgradeListener implements HttpUpgradeListener { + private final InstanceHandle instance; + private final ThreadSetupAction threadSetupAction; + private final HttpServerExchange exchange; + + public ServletUpgradeListener(final InstanceHandle instance, ThreadSetupAction threadSetupAction, HttpServerExchange exchange) { + this.instance = instance; + this.threadSetupAction = threadSetupAction; + this.exchange = exchange; + } + + @Override + public void handleUpgrade(final StreamConnection channel, final HttpServerExchange exchange) { + channel.getCloseSetter().set(new ChannelListener() { + @Override + public void handleEvent(StreamConnection channel) { + final ThreadSetupAction.Handle handle = threadSetupAction.setup(ServletUpgradeListener.this.exchange); + try { + instance.getInstance().destroy(); + } finally { + try { + handle.tearDown(); + } finally { + instance.release(); + } + } + } + }); + + this.exchange.getConnection().getWorker().execute(new Runnable() { + @Override + public void run() { + DelayedExecutor executor = new DelayedExecutor(exchange.getIoThread()); + final ThreadSetupAction.Handle handle = threadSetupAction.setup(ServletUpgradeListener.this.exchange); + try { + //run the upgrade in the worker thread + instance.getInstance().init(new WebConnectionImpl(channel, ServletUpgradeListener.this.exchange.getConnection().getBufferPool(), executor)); + } finally { + try { + handle.tearDown(); + } finally { + executor.openGate(); + } + } + } + }); + } + + /** + * Executor that delays submitting tasks to the delegate until a condition is satisfied. + */ + private static final class DelayedExecutor implements Executor { + + private final Executor delegate; + private volatile boolean queue = true; + private final List tasks = new ArrayList(); + + private DelayedExecutor(Executor delegate) { + this.delegate = delegate; + } + + @Override + public void execute(Runnable command) { + if (!queue) { + delegate.execute(command); + } else { + synchronized (this) { + if (!queue) { + delegate.execute(command); + } else { + tasks.add(command); + } + } + } + } + + synchronized void openGate() { + queue = false; + for (Runnable task : tasks) { + delegate.execute(task); + } + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/core/SessionListenerBridge.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/core/SessionListenerBridge.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/core/SessionListenerBridge.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,112 @@ +package io.undertow.servlet.core; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.server.session.SessionListener; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; + +import java.util.HashSet; + +/** + * Class that bridges between Undertow native session listeners and servlet ones. + * + * @author Stuart Douglas + */ +public class SessionListenerBridge implements SessionListener { + + public static final String IO_UNDERTOW = "io.undertow"; + private final ThreadSetupAction threadSetup; + private final ApplicationListeners applicationListeners; + private final ServletContext servletContext; + + public SessionListenerBridge(final ThreadSetupAction threadSetup, final ApplicationListeners applicationListeners, final ServletContext servletContext) { + this.threadSetup = threadSetup; + this.applicationListeners = applicationListeners; + this.servletContext = servletContext; + } + + @Override + public void sessionCreated(final Session session, final HttpServerExchange exchange) { + final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, true); + applicationListeners.sessionCreated(httpSession); + } + + @Override + public void sessionDestroyed(final Session session, final HttpServerExchange exchange, final SessionDestroyedReason reason) { + ThreadSetupAction.Handle handle = null; + try { + final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); + if (reason == SessionDestroyedReason.TIMEOUT) { + handle = threadSetup.setup(exchange); + } + applicationListeners.sessionDestroyed(httpSession); + //we make a defensive copy here, as there is no guarantee that the underlying session map + //is a concurrent map, and as a result a concurrent modification exception may be thrown + HashSet names = new HashSet(session.getAttributeNames()); + for(String attribute : names) { + session.removeAttribute(attribute); + } + } finally { + if (handle != null) { + handle.tearDown(); + } + ServletRequestContext current = SecurityActions.currentServletRequestContext(); + if (current != null) { + current.setSession(null); + } + } + } + + @Override + public void attributeAdded(final Session session, final String name, final Object value) { + if(name.startsWith(IO_UNDERTOW)) { + return; + } + final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); + applicationListeners.httpSessionAttributeAdded(httpSession, name, value); + if (value instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value)); + } + } + + @Override + public void attributeUpdated(final Session session, final String name, final Object value, final Object old) { + if(name.startsWith(IO_UNDERTOW)) { + return; + } + final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); + if (old != value) { + if (old instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old)); + } + applicationListeners.httpSessionAttributeReplaced(httpSession, name, old); + } + if (value instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(httpSession, name, value)); + } + } + + @Override + public void attributeRemoved(final Session session, final String name, final Object old) { + if(name.startsWith(IO_UNDERTOW)) { + return; + } + final HttpSessionImpl httpSession = SecurityActions.forSession(session, servletContext, false); + if (old != null) { + applicationListeners.httpSessionAttributeRemoved(httpSession, name, old); + if (old instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) old).valueUnbound(new HttpSessionBindingEvent(httpSession, name, old)); + } + } + } + + @Override + public void sessionIdChanged(Session session, String oldSessionId) { + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/DefaultServlet.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/DefaultServlet.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/DefaultServlet.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,350 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import io.undertow.io.IoCallback; +import io.undertow.io.Sender; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.resource.DirectoryUtils; +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.servlet.api.DefaultServletConfig; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.spec.ServletContextImpl; +import io.undertow.util.DateUtils; +import io.undertow.util.ETag; +import io.undertow.util.ETagUtils; +import io.undertow.util.Headers; +import io.undertow.util.Methods; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * Default servlet responsible for serving up resources. This is both a handler and a servlet. If no filters + * match the current path then the resources will be served up asynchronously using the + * {@link io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)} method, + * otherwise the request is handled as a normal servlet request. + *

+ * By default we only allow a restricted set of extensions. + *

+ * todo: this thing needs a lot more work. In particular: + * - caching for blocking requests + * - correct mime type + * - range/last-modified and other headers to be handled properly + * - head requests + * - and probably heaps of other things + * + * @author Stuart Douglas + */ +public class DefaultServlet extends HttpServlet { + + public static final String DIRECTORY_LISTING = "directory-listing"; + public static final String DEFAULT_ALLOWED = "default-allowed"; + public static final String ALLOWED_EXTENSIONS = "allowed-extensions"; + public static final String DISALLOWED_EXTENSIONS = "disallowed-extensions"; + public static final String RESOLVE_AGAINST_CONTEXT_ROOT = "resolve-against-context-root"; + + private static final Set DEFAULT_ALLOWED_EXTENSIONS = Collections.unmodifiableSet(new HashSet(Arrays.asList("js", "css", "png", "jpg", "gif", "html", "htm", "txt", "pdf", "jpeg", "xml"))); + private static final Set DEFAULT_DISALLOWED_EXTENSIONS = Collections.unmodifiableSet(new HashSet(Arrays.asList("class", "jar", "war"))); + + + private Deployment deployment; + private ResourceManager resourceManager; + private boolean directoryListingEnabled = false; + + private boolean defaultAllowed = true; + private Set allowed = DEFAULT_ALLOWED_EXTENSIONS; + private Set disallowed = DEFAULT_DISALLOWED_EXTENSIONS; + private boolean resolveAgainstContextRoot; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + ServletContextImpl sc = (ServletContextImpl) config.getServletContext(); + this.deployment = sc.getDeployment(); + DefaultServletConfig defaultServletConfig = deployment.getDeploymentInfo().getDefaultServletConfig(); + if (defaultServletConfig != null) { + defaultAllowed = defaultServletConfig.isDefaultAllowed(); + allowed = new HashSet(); + if (defaultServletConfig.getAllowed() != null) { + allowed.addAll(defaultServletConfig.getAllowed()); + } + disallowed = new HashSet(); + if (defaultServletConfig.getDisallowed() != null) { + disallowed.addAll(defaultServletConfig.getDisallowed()); + } + } + if (config.getInitParameter(DEFAULT_ALLOWED) != null) { + defaultAllowed = Boolean.parseBoolean(config.getInitParameter(DEFAULT_ALLOWED)); + } + if (config.getInitParameter(ALLOWED_EXTENSIONS) != null) { + String extensions = config.getInitParameter(ALLOWED_EXTENSIONS); + allowed = new HashSet(Arrays.asList(extensions.split(","))); + } + if (config.getInitParameter(DISALLOWED_EXTENSIONS) != null) { + String extensions = config.getInitParameter(DISALLOWED_EXTENSIONS); + disallowed = new HashSet(Arrays.asList(extensions.split(","))); + } + if (config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT) != null) { + resolveAgainstContextRoot = Boolean.parseBoolean(config.getInitParameter(RESOLVE_AGAINST_CONTEXT_ROOT)); + } + this.resourceManager = deployment.getDeploymentInfo().getResourceManager(); + String listings = config.getInitParameter(DIRECTORY_LISTING); + if (Boolean.valueOf(listings)) { + this.directoryListingEnabled = true; + } + } + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final String path = getPath(req); + if (!isAllowed(path)) { + resp.sendError(404); + return; + } + final Resource resource = resourceManager.getResource(path); + if (resource == null) { + if (req.getDispatcherType() == DispatcherType.INCLUDE) { + //servlet 9.3 + throw new FileNotFoundException(path); + } else { + resp.sendError(404); + } + return; + } else if (resource.isDirectory()) { + if ("css".equals(req.getQueryString())) { + resp.setContentType("text/css"); + resp.getWriter().write(DirectoryUtils.Blobs.FILE_CSS); + return; + } else if ("js".equals(req.getQueryString())) { + resp.setContentType("application/javascript"); + resp.getWriter().write(DirectoryUtils.Blobs.FILE_JS); + return; + } + if (directoryListingEnabled) { + StringBuilder output = DirectoryUtils.renderDirectoryListing(req.getRequestURI(), resource); + resp.getWriter().write(output.toString()); + } else { + resp.sendError(403); + } + } else { + serveFileBlocking(req, resp, resource); + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + /* + * Where a servlet has received a POST request we still require the capability to include static content. + */ + switch (req.getDispatcherType()) { + case INCLUDE: + case FORWARD: + case ERROR: + doGet(req, resp); + break; + default: + super.doPost(req, resp); + } + } + + @Override + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + switch (req.getDispatcherType()) { + case INCLUDE: + case FORWARD: + case ERROR: + doGet(req, resp); + break; + default: + super.doPut(req, resp); + } + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + switch (req.getDispatcherType()) { + case INCLUDE: + case FORWARD: + case ERROR: + doGet(req, resp); + break; + default: + super.doDelete(req, resp); + } + } + + @Override + protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + switch (req.getDispatcherType()) { + case INCLUDE: + case FORWARD: + case ERROR: + doGet(req, resp); + break; + default: + super.doOptions(req, resp); + } + } + + @Override + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + switch (req.getDispatcherType()) { + case INCLUDE: + case FORWARD: + case ERROR: + doGet(req, resp); + break; + default: + super.doTrace(req, resp); + } + } + + private void serveFileBlocking(final HttpServletRequest req, final HttpServletResponse resp, final Resource resource) throws IOException { + final ETag etag = resource.getETag(); + final Date lastModified = resource.getLastModified(); + if (!ETagUtils.handleIfMatch(req.getHeader(Headers.IF_MATCH_STRING), etag, false) || + !DateUtils.handleIfUnmodifiedSince(req.getHeader(Headers.IF_UNMODIFIED_SINCE_STRING), lastModified)) { + resp.setStatus(412); + return; + } + if (!ETagUtils.handleIfNoneMatch(req.getHeader(Headers.IF_NONE_MATCH_STRING), etag, true) || + !DateUtils.handleIfModifiedSince(req.getHeader(Headers.IF_MODIFIED_SINCE_STRING), lastModified)) { + resp.setStatus(304); + return; + } + //todo: handle range requests + //we are going to proceed. Set the appropriate headers + final String contentType = deployment.getServletContext().getMimeType(resource.getName()); + if (contentType != null) { + resp.setHeader(Headers.CONTENT_TYPE_STRING, contentType); + } else { + resp.setHeader(Headers.CONTENT_TYPE_STRING, "application/octet-stream"); + } + if (lastModified != null) { + resp.setHeader(Headers.LAST_MODIFIED_STRING, resource.getLastModifiedString()); + } + if (etag != null) { + resp.setHeader(Headers.ETAG_STRING, etag.toString()); + } + try { + //only set the content length if we are using a stream + //if we are using a writer who knows what the length will end up being + //todo: if someone installs a filter this can cause problems + //not sure how best to deal with this + Long contentLength = resource.getContentLength(); + if (contentLength != null) { + resp.getOutputStream(); + resp.setContentLengthLong(contentLength); + } + } catch (IllegalStateException e) { + + } + final boolean include = req.getDispatcherType() == DispatcherType.INCLUDE; + if (!req.getMethod().equals(Methods.HEAD_STRING)) { + HttpServerExchange exchange = SecurityActions.requireCurrentServletRequestContext().getOriginalRequest().getExchange(); + resource.serve(exchange.getResponseSender(), exchange, new IoCallback() { + + @Override + public void onComplete(final HttpServerExchange exchange, final Sender sender) { + if (!include) { + sender.close(); + } + } + + @Override + public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) { + //not much we can do here, the connection is broken + sender.close(); + } + }); + } + } + + private String getPath(final HttpServletRequest request) { + String servletPath; + String pathInfo; + + if (request.getDispatcherType() == DispatcherType.INCLUDE && request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) { + pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + + } else { + pathInfo = request.getPathInfo(); + servletPath = request.getServletPath(); + } + String result = pathInfo; + if (result == null) { + result = servletPath; + } else if(resolveAgainstContextRoot) { + result = servletPath + pathInfo; + } + if ((result == null) || (result.equals(""))) { + result = "/"; + } + return result; + + } + + private boolean isAllowed(String path) { + if (!path.isEmpty()) { + if (path.startsWith("/META-INF") || + path.startsWith("META-INF") || + path.startsWith("/WEB-INF") || + path.startsWith("WEB-INF")) { + return false; + } + } + int pos = path.lastIndexOf('/'); + final String lastSegment; + if (pos == -1) { + lastSegment = path; + } else { + lastSegment = path.substring(pos + 1); + } + if (lastSegment.isEmpty()) { + return true; + } + int ext = lastSegment.lastIndexOf('.'); + if (ext == -1) { + //no extension + return true; + } + final String extension = lastSegment.substring(ext + 1, lastSegment.length()); + if (defaultAllowed) { + return !disallowed.contains(extension); + } else { + return allowed.contains(extension); + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/FilterHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/FilterHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/FilterHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,149 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import java.io.IOException; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.core.ManagedFilter; +import io.undertow.servlet.spec.AsyncContextImpl; + +/** + * @author Stuart Douglas + */ +public class FilterHandler implements HttpHandler { + + private final Map> filters; + private final Map asyncSupported; + private final boolean allowNonStandardWrappers; + + private final HttpHandler next; + + public FilterHandler(final Map> filters, final boolean allowNonStandardWrappers, final HttpHandler next) { + this.allowNonStandardWrappers = allowNonStandardWrappers; + this.next = next; + this.filters = new EnumMap>(filters); + Map asyncSupported = new EnumMap(DispatcherType.class); + for(Map.Entry> entry : filters.entrySet()) { + boolean supported = true; + for(ManagedFilter i : entry.getValue()) { + if(!i.getFilterInfo().isAsyncSupported()) { + supported = false; + break; + } + } + asyncSupported.put(entry.getKey(), supported); + } + this.asyncSupported = asyncSupported; + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + ServletRequest request = servletRequestContext.getServletRequest(); + ServletResponse response = servletRequestContext.getServletResponse(); + DispatcherType dispatcher = servletRequestContext.getDispatcherType(); + Boolean supported = asyncSupported.get(dispatcher); + if(supported != null && ! supported) { + exchange.putAttachment(AsyncContextImpl.ASYNC_SUPPORTED, false ); + } + + final List filters = this.filters.get(dispatcher); + if(filters == null) { + next.handleRequest(exchange); + } else { + final FilterChainImpl filterChain = new FilterChainImpl(exchange, filters, next, allowNonStandardWrappers); + filterChain.doFilter(request, response); + } + } + + private static class FilterChainImpl implements FilterChain { + + int location = 0; + final HttpServerExchange exchange; + final List filters; + final HttpHandler next; + final boolean allowNonStandardWrappers; + + private FilterChainImpl(final HttpServerExchange exchange, final List filters, final HttpHandler next, final boolean allowNonStandardWrappers) { + this.exchange = exchange; + this.filters = filters; + this.next = next; + this.allowNonStandardWrappers = allowNonStandardWrappers; + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response) throws IOException, ServletException { + + + + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + final ServletRequest oldReq = servletRequestContext.getServletRequest(); + final ServletResponse oldResp = servletRequestContext.getServletResponse(); + try { + + if(!allowNonStandardWrappers) { + if(oldReq != request) { + if(!(request instanceof ServletRequestWrapper)) { + throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); + } + } + if(oldResp != response) { + if(!(response instanceof ServletResponseWrapper)) { + throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); + } + } + } + servletRequestContext.setServletRequest(request); + servletRequestContext.setServletResponse(response); + int index = location++; + if (index >= filters.size()) { + next.handleRequest(exchange); + } else { + filters.get(index).doFilter(request, response, this); + } + } catch (IOException e) { + throw e; + } catch (ServletException e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + location--; + servletRequestContext.setServletRequest(oldReq); + servletRequestContext.setServletResponse(oldResp); + } + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/SecurityActions.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/SecurityActions.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/SecurityActions.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,82 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.ServletContext; + +import io.undertow.server.session.Session; +import io.undertow.servlet.spec.HttpSessionImpl; + +class SecurityActions { + static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { + if (System.getSecurityManager() == null) { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public HttpSessionImpl run() { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } + }); + } + } + + static void setCurrentRequestContext(final ServletRequestContext servletRequestContext) { + if (System.getSecurityManager() == null) { + ServletRequestContext.setCurrentRequestContext(servletRequestContext); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + ServletRequestContext.setCurrentRequestContext(servletRequestContext); + return null; + } + }); + } + } + + static ServletRequestContext requireCurrentServletRequestContext() { + if (System.getSecurityManager() == null) { + return ServletRequestContext.requireCurrent(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletRequestContext run() { + return ServletRequestContext.requireCurrent(); + } + }); + } + } + + static void clearCurrentServletAttachments() { + if (System.getSecurityManager() == null) { + ServletRequestContext.clearCurrentServletAttachments(); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + ServletRequestContext.clearCurrentServletAttachments(); + return null; + } + }); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletChain.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletChain.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletChain.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,53 @@ +package io.undertow.servlet.handlers; + +import java.util.concurrent.Executor; + +import io.undertow.server.HttpHandler; +import io.undertow.servlet.core.ManagedServlet; + +/** +* @author Stuart Douglas +*/ +public class ServletChain { + private final HttpHandler handler; + private final ManagedServlet managedServlet; + private final String servletPath; + private final Executor executor; + private final boolean defaultServletMapping; + + public ServletChain(final HttpHandler handler, final ManagedServlet managedServlet, final String servletPath, boolean defaultServletMapping) { + this.handler = handler; + this.managedServlet = managedServlet; + this.servletPath = servletPath; + this.defaultServletMapping = defaultServletMapping; + this.executor = managedServlet.getServletInfo().getExecutor(); + } + + public ServletChain(final ServletChain other) { + this(other.getHandler(), other.getManagedServlet(), other.getServletPath(), other.isDefaultServletMapping()); + } + + public HttpHandler getHandler() { + return handler; + } + + public ManagedServlet getManagedServlet() { + return managedServlet; + } + + /** + * + * @return The servlet path part + */ + public String getServletPath() { + return servletPath; + } + + public Executor getExecutor() { + return executor; + } + + public boolean isDefaultServletMapping() { + return defaultServletMapping; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDebugPageHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDebugPageHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDebugPageHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,107 @@ +package io.undertow.servlet.handlers; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.spec.HttpServletRequestImpl; +import io.undertow.util.Headers; + +import java.io.IOException; + +/** + * generates a servlet error page with a stack trace + * + * @author Stuart Douglas + */ +public class ServletDebugPageHandler { + + + public static final String ERROR_CSS = + ""; + + + public static void handleRequest(HttpServerExchange exchange, final ServletRequestContext servletRequestContext, final Throwable exception) throws IOException { + HttpServletRequestImpl req = servletRequestContext.getOriginalRequest(); + StringBuilder sb = new StringBuilder(); + //todo: make this good + sb.append("ERROR"); + sb.append(ERROR_CSS); + sb.append("
Error processing request
"); + writeLabel(sb, "Context Path", req.getContextPath()); + writeLabel(sb, "Servlet Path", req.getServletPath()); + writeLabel(sb, "Path Info", req.getPathInfo()); + writeLabel(sb, "Query String", req.getQueryString()); + sb.append("Stack Trace
"); + sb.append(escapeBodyText(exception.toString())); + sb.append("
"); + for(StackTraceElement element : exception.getStackTrace()) { + sb.append(escapeBodyText(element.toString())); + sb.append("
"); + } + sb.append(""); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=UTF-8"); + exchange.getResponseSender().send(sb.toString()); + } + + private static void writeLabel(StringBuilder sb, String label, String value) { + sb.append("
"); + sb.append(escapeBodyText(label)); + sb.append(":
"); + sb.append(escapeBodyText(value)); + sb.append("

"); + + } + + + public static String escapeBodyText(final String bodyText) { + if(bodyText == null) { + return "null"; + } + return bodyText.replace("&", "&").replace("<", "<").replace(">", ">"); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDispatchingHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDispatchingHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletDispatchingHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,39 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; + +/** + * Handler that dispatches to the resolved servlet. + * + * @author Stuart Douglas + */ +public class ServletDispatchingHandler implements HttpHandler { + + public static final ServletDispatchingHandler INSTANCE = new ServletDispatchingHandler(); + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + ServletChain info = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet(); + info.getHandler().handleRequest(exchange); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,118 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import java.io.IOException; +import java.util.Date; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; + +import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpHandler; +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.core.ManagedServlet; +import io.undertow.servlet.spec.AsyncContextImpl; + +/** + * The handler that is responsible for invoking the servlet + *

+ * TODO: do we want to move lifecycle considerations out of this handler? + * + * @author Stuart Douglas + */ +public class ServletHandler implements HttpHandler { + + private final ManagedServlet managedServlet; + + private static final AtomicLongFieldUpdater unavailableUntilUpdater = AtomicLongFieldUpdater.newUpdater(ServletHandler.class, "unavailableUntil"); + + @SuppressWarnings("unused") + private volatile long unavailableUntil = 0; + + public ServletHandler(final ManagedServlet managedServlet) { + this.managedServlet = managedServlet; + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws IOException, ServletException { + if (managedServlet.isPermanentlyUnavailable()) { + UndertowServletLogger.REQUEST_LOGGER.debugf("Returning 404 for servlet %s due to permanent unavailability", managedServlet.getServletInfo().getName()); + exchange.setResponseCode(404); + return; + } + + long until = unavailableUntil; + if (until != 0) { + UndertowServletLogger.REQUEST_LOGGER.debugf("Returning 503 for servlet %s due to temporary unavailability", managedServlet.getServletInfo().getName()); + if (System.currentTimeMillis() < until) { + exchange.setResponseCode(503); + return; + } else { + unavailableUntilUpdater.compareAndSet(this, until, 0); + } + } + if(!managedServlet.getServletInfo().isAsyncSupported()) { + exchange.putAttachment(AsyncContextImpl.ASYNC_SUPPORTED, false); + } + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + ServletRequest request = servletRequestContext.getServletRequest(); + ServletResponse response = servletRequestContext.getServletResponse(); + InstanceHandle servlet = null; + try { + servlet = managedServlet.getServlet(); + servlet.getInstance().service(request, response); + + //according to the spec we have to call AsyncContext.complete() at this point + //straight after the service method + //not super sure about this, surely it would make more sense to do this when the request has returned to the container, however the spec is quite clear wording wise + //todo: should we actually enable this? Apparently other containers do not do it + //if(!request.isAsyncStarted()) { + // AsyncContextImpl existingAsyncContext = servletRequestContext.getOriginalRequest().getAsyncContextInternal(); + // if (existingAsyncContext != null) { + // existingAsyncContext.complete(); + // } + //} + } catch (UnavailableException e) { + if (e.isPermanent()) { + UndertowServletLogger.REQUEST_LOGGER.stoppingServletDueToPermanentUnavailability(managedServlet.getServletInfo().getName(), e); + managedServlet.stop(); + managedServlet.setPermanentlyUnavailable(true); + exchange.setResponseCode(404); + } else { + unavailableUntilUpdater.set(this, System.currentTimeMillis() + e.getUnavailableSeconds() * 1000); + UndertowServletLogger.REQUEST_LOGGER.stoppingServletUntilDueToTemporaryUnavailability(managedServlet.getServletInfo().getName(), new Date(until), e); + exchange.setResponseCode(503); + } + } finally { + if(servlet != null) { + servlet.release(); + } + } + } + + public ManagedServlet getManagedServlet() { + return managedServlet; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletInitialHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletInitialHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletInitialHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,438 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import io.undertow.UndertowLogger; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpUpgradeListener; +import io.undertow.server.SSLSessionInfo; +import io.undertow.server.ServerConnection; +import io.undertow.servlet.api.ServletDispatcher; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.core.ApplicationListeners; +import io.undertow.servlet.core.CompositeThreadSetupAction; +import io.undertow.servlet.core.ServletBlockingHttpExchange; +import io.undertow.servlet.spec.HttpServletRequestImpl; +import io.undertow.servlet.spec.HttpServletResponseImpl; +import io.undertow.servlet.spec.RequestDispatcherImpl; +import io.undertow.servlet.spec.ServletContextImpl; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.Protocols; +import io.undertow.util.RedirectBuilder; +import org.xnio.BufferAllocator; +import org.xnio.ByteBufferSlicePool; +import org.xnio.ChannelListener; +import org.xnio.Option; +import org.xnio.OptionMap; +import org.xnio.Pool; +import org.xnio.StreamConnection; +import org.xnio.XnioIoThread; +import org.xnio.XnioWorker; +import org.xnio.channels.ConnectedChannel; +import org.xnio.conduits.ConduitStreamSinkChannel; +import org.xnio.conduits.ConduitStreamSourceChannel; +import org.xnio.conduits.StreamSinkConduit; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.concurrent.Executor; + +/** + * This must be the initial handler in the blocking servlet chain. This sets up the request and response objects, + * and attaches them the to exchange. + * + * @author Stuart Douglas + */ +public class ServletInitialHandler implements HttpHandler, ServletDispatcher { + + private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.CREATE_INITIAL_HANDLER"); + + private final HttpHandler next; + //private final HttpHandler asyncPath; + + private final CompositeThreadSetupAction setupAction; + + private final ServletContextImpl servletContext; + + private final ApplicationListeners listeners; + + private final ServletPathMatches paths; + + public ServletInitialHandler(final ServletPathMatches paths, final HttpHandler next, final CompositeThreadSetupAction setupAction, final ServletContextImpl servletContext) { + this.next = next; + this.setupAction = setupAction; + this.servletContext = servletContext; + this.paths = paths; + this.listeners = servletContext.getDeployment().getApplicationListeners(); + if(System.getSecurityManager() != null) { + //handle request can use doPrivilidged + //we need to make sure this is not abused + AccessController.checkPermission(PERMISSION); + } + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + final String path = exchange.getRelativePath(); + if(isForbiddenPath(path)) { + exchange.setResponseCode(404); + return; + } + final ServletPathMatch info = paths.getServletHandlerByPath(path); + if (info.getType() == ServletPathMatch.Type.REDIRECT) { + //UNDERTOW-89 + //we redirect on GET requests to the root context to add an / to the end + exchange.setResponseCode(302); + exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, exchange.getRelativePath() + "/", true)); + return; + } else if (info.getType() == ServletPathMatch.Type.REWRITE) { + //this can only happen if the path ends with a / + //otherwise there would be a rewrite instead + exchange.setRelativePath(exchange.getRelativePath() + info.getRewriteLocation()); + exchange.setRequestURI(exchange.getRequestURI() + info.getRewriteLocation()); + exchange.setRequestPath(exchange.getRequestPath() + info.getRewriteLocation()); + } + + final HttpServletResponseImpl response = new HttpServletResponseImpl(exchange, servletContext); + final HttpServletRequestImpl request = new HttpServletRequestImpl(exchange, servletContext); + final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), request, response, info); + //set the max request size if applicable + if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0) { + exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize()); + } + exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext); + + exchange.startBlocking(new ServletBlockingHttpExchange(exchange)); + servletRequestContext.setServletPathMatch(info); + + Executor executor = info.getServletChain().getExecutor(); + if (executor == null) { + executor = servletContext.getDeployment().getExecutor(); + } + + if (exchange.isInIoThread() || executor != null) { + //either the exchange has not been dispatched yet, or we need to use a special executor + exchange.dispatch(executor, new HttpHandler() { + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + if(System.getSecurityManager() == null) { + dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); + } else { + //sometimes thread pools inherit some random + AccessController.doPrivileged(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception{ + dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); + return null; + } + }); + } + } + }); + } else { + dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); + } + } + + private boolean isForbiddenPath(String path) { + return path.equalsIgnoreCase("/meta-inf/") + || path.regionMatches(true, 0, "/web-inf/", 0, "/web-inf/".length()); + } + + public void dispatchToPath(final HttpServerExchange exchange, final ServletPathMatch pathInfo, final DispatcherType dispatcherType) throws Exception { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + servletRequestContext.setServletPathMatch(pathInfo); + dispatchRequest(exchange, servletRequestContext, pathInfo.getServletChain(), dispatcherType); + } + + @Override + public void dispatchToServlet(final HttpServerExchange exchange, final ServletChain servletchain, final DispatcherType dispatcherType) throws Exception { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + dispatchRequest(exchange, servletRequestContext, servletchain, dispatcherType); + } + + @Override + public void dispatchMockRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException { + + final ByteBufferSlicePool bufferPool = new ByteBufferSlicePool(BufferAllocator.BYTE_BUFFER_ALLOCATOR, 1024, 1024); + MockServerConnection connection = new MockServerConnection(bufferPool); + HttpServerExchange exchange = new HttpServerExchange(connection); + exchange.setRequestScheme(request.getScheme()); + exchange.setRequestMethod(new HttpString(request.getMethod())); + exchange.setProtocol(Protocols.HTTP_1_0); + exchange.setResolvedPath(request.getContextPath()); + String relative; + if (request.getPathInfo() == null) { + relative = request.getServletPath(); + } else { + relative = request.getServletPath() + request.getPathInfo(); + } + exchange.setRelativePath(relative); + final ServletPathMatch info = paths.getServletHandlerByPath(request.getServletPath()); + final HttpServletResponseImpl oResponse = new HttpServletResponseImpl(exchange, servletContext); + final HttpServletRequestImpl oRequest = new HttpServletRequestImpl(exchange, servletContext); + final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), oRequest, oResponse, info); + servletRequestContext.setServletRequest(request); + servletRequestContext.setServletResponse(response); + //set the max request size if applicable + if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0) { + exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize()); + } + exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext); + + exchange.startBlocking(new ServletBlockingHttpExchange(exchange)); + servletRequestContext.setServletPathMatch(info); + + try { + dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new ServletException(e); + } + } + + private void dispatchRequest(final HttpServerExchange exchange, final ServletRequestContext servletRequestContext, final ServletChain servletChain, final DispatcherType dispatcherType) throws Exception { + servletRequestContext.setDispatcherType(dispatcherType); + servletRequestContext.setCurrentServlet(servletChain); + if (dispatcherType == DispatcherType.REQUEST || dispatcherType == DispatcherType.ASYNC) { + handleFirstRequest(exchange, servletChain, servletRequestContext, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse()); + } else { + next.handleRequest(exchange); + } + } + + public void handleFirstRequest(final HttpServerExchange exchange, final ServletChain servletChain, final ServletRequestContext servletRequestContext, final ServletRequest request, final ServletResponse response) throws Exception { + + ThreadSetupAction.Handle handle = setupAction.setup(exchange); + try { + SecurityActions.setCurrentRequestContext(servletRequestContext); + try { + listeners.requestInitialized(request); + next.handleRequest(exchange); + // + } catch (Throwable t) { + + if(t instanceof IOException) { + //we log IOExceptions at a lower level + //because they can be easily caused by malicious remote clients in at attempt to DOS the server by filling the logs + UndertowLogger.REQUEST_IO_LOGGER.debugf(t, "Exception handling request to %s", exchange.getRequestURI()); + } else { + UndertowLogger.REQUEST_LOGGER.exceptionHandlingRequest(t, exchange.getRequestURI()); + } + if (request.isAsyncStarted() || request.getDispatcherType() == DispatcherType.ASYNC) { + exchange.unDispatch(); + servletRequestContext.getOriginalRequest().getAsyncContextInternal().handleError(t); + } else { + if (!exchange.isResponseStarted()) { + response.reset(); //reset the response + exchange.setResponseCode(500); + exchange.getResponseHeaders().clear(); + String location = servletContext.getDeployment().getErrorPages().getErrorLocation(t); + if (location != null) { + RequestDispatcherImpl dispatcher = new RequestDispatcherImpl(location, servletContext); + try { + dispatcher.error(request, response, servletChain.getManagedServlet().getServletInfo().getName(), t); + } catch (Exception e) { + UndertowLogger.REQUEST_LOGGER.exceptionGeneratingErrorPage(e, location); + } + } else { + if (servletRequestContext.displayStackTraces()) { + ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, t); + } else { + //TODO: we need a debug mode to generate a debug error page + if (response instanceof HttpServletResponse) { + ((HttpServletResponse) response).sendError(500); + } else { + servletRequestContext.getOriginalResponse().sendError(500); + } + } + } + } + } + + } finally { + listeners.requestDestroyed(request); + } + //if it is not dispatched and is not a mock request + if (!exchange.isDispatched() && !(exchange.getConnection() instanceof MockServerConnection)) { + servletRequestContext.getOriginalResponse().responseDone(); + } + } finally { + try { + handle.tearDown(); + } finally { + SecurityActions.clearCurrentServletAttachments(); + } + } + } + + public HttpHandler getNext() { + return next; + } + + private static class MockServerConnection extends ServerConnection { + private final Pool bufferPool; + private SSLSessionInfo sslSessionInfo; + + private MockServerConnection(Pool bufferPool) { + this.bufferPool = bufferPool; + } + + @Override + public Pool getBufferPool() { + return bufferPool; + } + + @Override + public XnioWorker getWorker() { + return null; + } + + @Override + public XnioIoThread getIoThread() { + return null; + } + + @Override + public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) { + throw new IllegalStateException(); + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public boolean supportsOption(Option option) { + return false; + } + + @Override + public T getOption(Option option) throws IOException { + return null; + } + + @Override + public T setOption(Option option, T value) throws IllegalArgumentException, IOException { + return null; + } + + @Override + public void close() throws IOException { + } + + @Override + public SocketAddress getPeerAddress() { + return null; + } + + @Override + public A getPeerAddress(Class type) { + return null; + } + + @Override + public ChannelListener.Setter getCloseSetter() { + return null; + } + + @Override + public SocketAddress getLocalAddress() { + return null; + } + + @Override + public A getLocalAddress(Class type) { + return null; + } + + @Override + public OptionMap getUndertowOptions() { + return OptionMap.EMPTY; + } + + @Override + public int getBufferSize() { + return 1024; + } + + @Override + public SSLSessionInfo getSslSessionInfo() { + return sslSessionInfo; + } + + @Override + public void setSslSessionInfo(SSLSessionInfo sessionInfo) { + sslSessionInfo = sessionInfo; + } + + @Override + public void addCloseListener(CloseListener listener) { + } + + @Override + public StreamConnection upgradeChannel() { + return null; + } + + @Override + public ConduitStreamSinkChannel getSinkChannel() { + return null; + } + + @Override + public ConduitStreamSourceChannel getSourceChannel() { + return new ConduitStreamSourceChannel(null, null); + } + + @Override + protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) { + return conduit; + } + + @Override + protected boolean isUpgradeSupported() { + return false; + } + + @Override + protected void exchangeComplete(HttpServerExchange exchange) { + } + + @Override + protected void setUpgradeListener(HttpUpgradeListener upgradeListener) { + //ignore + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatch.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatch.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatch.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,100 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +/** + * @author Stuart Douglas + */ +public class ServletPathMatch { + + private final String matched; + private final String remaining; + private final boolean requiredWelcomeFileMatch; + private final ServletChain servletChain; + private final String rewriteLocation; + private final Type type; + + public ServletPathMatch(final ServletChain target, final String uri, boolean requiredWelcomeFileMatch) { + this.servletChain = target; + this.requiredWelcomeFileMatch = requiredWelcomeFileMatch; + this.type = Type.NORMAL; + this.rewriteLocation = null; + if (target.getServletPath() == null) { + //the default servlet is always considered to have matched the full path. + this.matched = uri; + this.remaining = null; + } else { + this.matched = target.getServletPath(); + if(uri.length() == matched.length()) { + remaining = null; + } else { + remaining = uri.substring(matched.length()); + } + } + } + + public ServletPathMatch(final ServletChain target, final String matched, final String remaining, final Type type, final String rewriteLocation) { + this.servletChain = target; + this.matched = matched; + this.remaining = remaining; + this.requiredWelcomeFileMatch = false; + this.type = type; + this.rewriteLocation = rewriteLocation; + } + + public String getMatched() { + return matched; + } + + public String getRemaining() { + return remaining; + } + + public boolean isRequiredWelcomeFileMatch() { + return requiredWelcomeFileMatch; + } + + public ServletChain getServletChain() { + return servletChain; + } + + public String getRewriteLocation() { + return rewriteLocation; + } + + public Type getType() { + return type; + } + + public static enum Type { + /** + * A normal servlet match, the invocation should proceed as normal + */ + NORMAL, + /** + * A redirect is required, as the path does not end with a trailing slash + */ + REDIRECT, + /** + * An internal rewrite is required, because the path matched a welcome file. + * The provided match data is the match data after the rewrite. + */ + REWRITE; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatches.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatches.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatches.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,449 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import io.undertow.server.HandlerWrapper; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterMappingInfo; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.core.ManagedFilter; +import io.undertow.servlet.core.ManagedFilters; +import io.undertow.servlet.core.ManagedServlet; +import io.undertow.servlet.core.ManagedServlets; +import io.undertow.servlet.handlers.security.ServletSecurityRoleHandler; + +import javax.servlet.DispatcherType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.undertow.servlet.handlers.ServletPathMatch.Type.REDIRECT; +import static io.undertow.servlet.handlers.ServletPathMatch.Type.REWRITE; + +/** + * Facade around {@link ServletPathMatchesData}. This facade is responsible for re-generating the matches if anything changes. + * + * @author Stuart Douglas + */ +public class ServletPathMatches { + + public static final String DEFAULT_SERVLET_NAME = "default"; + private final Deployment deployment; + + private final String[] welcomePages; + private final ResourceManager resourceManager; + + private volatile ServletPathMatchesData data; + + public ServletPathMatches(final Deployment deployment) { + this.deployment = deployment; + this.welcomePages = deployment.getDeploymentInfo().getWelcomePages().toArray(new String[deployment.getDeploymentInfo().getWelcomePages().size()]); + this.resourceManager = deployment.getDeploymentInfo().getResourceManager(); + } + + public ServletChain getServletHandlerByName(final String name) { + return getData().getServletHandlerByName(name); + } + + public ServletPathMatch getServletHandlerByPath(final String path) { + ServletPathMatch match = getData().getServletHandlerByPath(path); + if (!match.isRequiredWelcomeFileMatch()) { + return match; + } + try { + + String remaining = match.getRemaining() == null ? match.getMatched() : match.getRemaining(); + Resource resource = resourceManager.getResource(remaining); + if (resource == null || !resource.isDirectory()) { + return match; + } + + boolean pathEndsWithSlash = remaining.endsWith("/"); + final String pathWithTrailingSlash = pathEndsWithSlash ? remaining : remaining + "/"; + + ServletPathMatch welcomePage = findWelcomeFile(pathWithTrailingSlash, !pathEndsWithSlash); + + if (welcomePage != null) { + return welcomePage; + } else { + welcomePage = findWelcomeServlet(pathWithTrailingSlash, !pathEndsWithSlash); + if (welcomePage != null) { + return welcomePage; + } else if(pathEndsWithSlash) { + return match; + } else { + return new ServletPathMatch(match.getServletChain(), match.getMatched(), match.getRemaining(), REDIRECT, "/"); + } + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public void invalidate() { + this.data = null; + } + + private ServletPathMatchesData getData() { + ServletPathMatchesData data = this.data; + if (data != null) { + return data; + } + synchronized (this) { + if (this.data != null) { + return this.data; + } + return this.data = setupServletChains(); + } + } + + private ServletPathMatch findWelcomeFile(final String path, boolean requiresRedirect) { + for (String i : welcomePages) { + try { + String mergedPath = path + i; + Resource resource = resourceManager.getResource(mergedPath); + if (resource != null) { + final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath); + return new ServletPathMatch(handler.getServletChain(), mergedPath, null, requiresRedirect ? REDIRECT : REWRITE, i); + } + } catch (IOException e) { + } + } + return null; + } + + private ServletPathMatch findWelcomeServlet(final String path, boolean requiresRedirect) { + for (String i : welcomePages) { + String mergedPath = path + i; + final ServletPathMatch handler = data.getServletHandlerByPath(mergedPath); + if (handler != null && !handler.isRequiredWelcomeFileMatch()) { + return new ServletPathMatch(handler.getServletChain(), handler.getMatched(), handler.getRemaining(), requiresRedirect ? REDIRECT : REWRITE, i); + } + } + return null; + } + + /** + * Sets up the handlers in the servlet chain. We setup a chain for every path + extension match possibility. + * (i.e. if there a m path mappings and n extension mappings we have n*m chains). + *

+ * If a chain consists of only the default servlet then we add it as an async handler, so that resources can be + * served up directly without using blocking operations. + *

+ * TODO: this logic is a bit convoluted at the moment, we should look at simplifying it + */ + private ServletPathMatchesData setupServletChains() { + //create the default servlet + ServletHandler defaultServlet = null; + final ManagedServlets servlets = deployment.getServlets(); + final ManagedFilters filters = deployment.getFilters(); + + final Map extensionServlets = new HashMap(); + final Map pathServlets = new HashMap(); + + final Set pathMatches = new HashSet(); + final Set extensionMatches = new HashSet(); + + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + + //loop through all filter mappings, and add them to the set of known paths + for (FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { + if (mapping.getMappingType() == FilterMappingInfo.MappingType.URL) { + String path = mapping.getMapping(); + if (path.equals("*")) { + //UNDERTOW-95, support this non-standard filter mapping + path = "/*"; + } + if (!path.startsWith("*.")) { + pathMatches.add(path); + } else { + extensionMatches.add(path.substring(2)); + } + } + } + + //now loop through all servlets. + for (Map.Entry entry : servlets.getServletHandlers().entrySet()) { + final ServletHandler handler = entry.getValue(); + //add the servlet to the approprite path maps + for (String path : handler.getManagedServlet().getServletInfo().getMappings()) { + if (path.equals("/")) { + //the default servlet + pathMatches.add("/*"); + if (defaultServlet != null) { + throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path); + } + defaultServlet = handler; + } else if (!path.startsWith("*.")) { + //either an exact or a /* based path match + if (path.isEmpty()) { + path = "/"; + } + pathMatches.add(path); + if (pathServlets.containsKey(path)) { + throw UndertowServletMessages.MESSAGES.twoServletsWithSameMapping(path); + } + pathServlets.put(path, handler); + } else { + //an extension match based servlet + String ext = path.substring(2); + extensionMatches.add(ext); + extensionServlets.put(ext, handler); + } + } + } + ServletHandler managedDefaultServlet = servlets.getServletHandler(DEFAULT_SERVLET_NAME); + if(managedDefaultServlet == null) { + //we always create a default servlet, even if it is not going to have any path mappings registered + managedDefaultServlet = servlets.addServlet(new ServletInfo(DEFAULT_SERVLET_NAME, DefaultServlet.class)); + } + + if (defaultServlet == null) { + //no explicit default servlet was specified, so we register our mapping + pathMatches.add("/*"); + defaultServlet = managedDefaultServlet; + } + + final ServletPathMatchesData.Builder builder = ServletPathMatchesData.builder(); + + //we now loop over every path in the application, and build up the patches based on this path + //these paths contain both /* and exact matches. + for (final String path : pathMatches) { + //resolve the target servlet, will return null if this is the default servlet + MatchData targetServletMatch = resolveServletForPath(path, pathServlets, extensionServlets, defaultServlet); + + final Map> noExtension = new EnumMap>(DispatcherType.class); + final Map>> extension = new HashMap>>(); + //initalize the extension map. This contains all the filers in the noExtension map, plus + //any filters that match the extension key + for (String ext : extensionMatches) { + extension.put(ext, new EnumMap>(DispatcherType.class)); + } + + //loop over all the filters, and add them to the appropriate map in the correct order + for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) { + ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName()); + if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { + if (targetServletMatch.handler != null) { + if (filterMapping.getMapping().equals(targetServletMatch.handler.getManagedServlet().getServletInfo().getName())) { + addToListMap(noExtension, filterMapping.getDispatcher(), filter); + } + } + for(Map.Entry>> entry : extension.entrySet()) { + ServletHandler pathServlet = targetServletMatch.handler; + boolean defaultServletMatch = targetServletMatch.defaultServlet; + if (defaultServletMatch && extensionServlets.containsKey(entry.getKey())) { + pathServlet = extensionServlets.get(entry.getKey()); + } + + if (filterMapping.getMapping().equals(pathServlet.getManagedServlet().getServletInfo().getName())) { + addToListMap(extension.get(entry.getKey()), filterMapping.getDispatcher(), filter); + } + } + } else { + if (filterMapping.getMapping().isEmpty() || !filterMapping.getMapping().startsWith("*.")) { + if (isFilterApplicable(path, filterMapping.getMapping())) { + addToListMap(noExtension, filterMapping.getDispatcher(), filter); + for (Map> l : extension.values()) { + addToListMap(l, filterMapping.getDispatcher(), filter); + } + } + } else { + addToListMap(extension.get(filterMapping.getMapping().substring(2)), filterMapping.getDispatcher(), filter); + } + } + } + //resolve any matches and add them to the builder + if (path.endsWith("/*")) { + String prefix = path.substring(0, path.length() - 2); + //add the default non-extension match + builder.addPrefixMatch(prefix, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet), targetServletMatch.defaultServlet || targetServletMatch.handler.getManagedServlet().getServletInfo().isRequireWelcomeFileMapping()); + + //build up the chain for each non-extension match + for (Map.Entry>> entry : extension.entrySet()) { + ServletHandler pathServlet = targetServletMatch.handler; + String pathMatch = targetServletMatch.matchedPath; + + boolean defaultServletMatch = targetServletMatch.defaultServlet; + if (defaultServletMatch && extensionServlets.containsKey(entry.getKey())) { + defaultServletMatch = false; + pathServlet = extensionServlets.get(entry.getKey()); + } + HttpHandler handler = pathServlet; + if (!entry.getValue().isEmpty()) { + handler = new FilterHandler(entry.getValue(), deploymentInfo.isAllowNonStandardWrappers(), handler); + } + builder.addExtensionMatch(prefix, entry.getKey(), servletChain(handler, pathServlet.getManagedServlet(), pathMatch, deploymentInfo, defaultServletMatch)); + } + } else if (path.isEmpty()) { + //the context root match + builder.addExactMatch("/", createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet)); + } else { + //we need to check for an extension match, so paths like /exact.txt will have the correct filter applied + String lastSegment = path.substring(path.lastIndexOf('/')); + if (lastSegment.contains(".")) { + String ext = lastSegment.substring(lastSegment.lastIndexOf('.') + 1); + if (extension.containsKey(ext)) { + Map> extMap = extension.get(ext); + builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, extMap, targetServletMatch.matchedPath, targetServletMatch.defaultServlet)); + } else { + builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet)); + } + } else { + builder.addExactMatch(path, createHandler(deploymentInfo, targetServletMatch.handler, noExtension, targetServletMatch.matchedPath, targetServletMatch.defaultServlet)); + } + + } + } + + //now setup name based mappings + //these are used for name based dispatch + for (Map.Entry entry : servlets.getServletHandlers().entrySet()) { + final Map> filtersByDispatcher = new EnumMap>(DispatcherType.class); + for (final FilterMappingInfo filterMapping : deploymentInfo.getFilterMappings()) { + ManagedFilter filter = filters.getManagedFilter(filterMapping.getFilterName()); + if (filterMapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { + if (filterMapping.getMapping().equals(entry.getKey())) { + addToListMap(filtersByDispatcher, filterMapping.getDispatcher(), filter); + } + } + } + if (filtersByDispatcher.isEmpty()) { + builder.addNameMatch(entry.getKey(), servletChain(entry.getValue(), entry.getValue().getManagedServlet(), null, deploymentInfo, false)); + } else { + builder.addNameMatch(entry.getKey(), servletChain(new FilterHandler(filtersByDispatcher, deploymentInfo.isAllowNonStandardWrappers(), entry.getValue()), entry.getValue().getManagedServlet(), null, deploymentInfo, false)); + } + } + + return builder.build(); + } + + private ServletChain createHandler(final DeploymentInfo deploymentInfo, final ServletHandler targetServlet, final Map> noExtension, final String servletPath, final boolean defaultServlet) { + final ServletChain initialHandler; + if (noExtension.isEmpty()) { + initialHandler = servletChain(targetServlet, targetServlet.getManagedServlet(), servletPath, deploymentInfo, defaultServlet); + } else { + FilterHandler handler = new FilterHandler(noExtension, deploymentInfo.isAllowNonStandardWrappers(), targetServlet); + initialHandler = servletChain(handler, targetServlet.getManagedServlet(), servletPath, deploymentInfo, defaultServlet); + } + return initialHandler; + } + + private static MatchData resolveServletForPath(final String path, final Map pathServlets, final Map extensionServlets, ServletHandler defaultServlet) { + if (pathServlets.containsKey(path)) { + if (path.endsWith("/*")) { + final String base = path.substring(0, path.length() - 2); + return new MatchData(pathServlets.get(path), base, false); + } else { + return new MatchData(pathServlets.get(path), path, false); + } + } + String match = null; + ServletHandler servlet = null; + for (final Map.Entry entry : pathServlets.entrySet()) { + String key = entry.getKey(); + if (key.endsWith("/*")) { + final String base = key.substring(0, key.length() - 2); + if (match == null || base.length() > match.length()) { + if (path.startsWith(base)) { + match = base; + servlet = entry.getValue(); + } + } + } + } + if (servlet != null) { + return new MatchData(servlet, match, false); + } + int index = path.lastIndexOf('.'); + if (index != -1) { + String ext = path.substring(index + 1); + servlet = extensionServlets.get(ext); + if (servlet != null) { + return new MatchData(servlet, null, false); + } + } + + return new MatchData(defaultServlet, null, true); + } + + private static boolean isFilterApplicable(final String path, final String filterPath) { + String modifiedPath; + if (filterPath.equals("*")) { + modifiedPath = "/*"; + } else { + modifiedPath = filterPath; + } + if (path.isEmpty()) { + return modifiedPath.equals("/*") || modifiedPath.equals("/"); + } + if (modifiedPath.endsWith("/*")) { + String baseFilterPath = modifiedPath.substring(0, modifiedPath.length() - 1); + return path.startsWith(baseFilterPath); + } else { + return modifiedPath.equals(path); + } + } + + private static void addToListMap(final Map> map, final K key, final V value) { + List list = map.get(key); + if (list == null) { + map.put(key, list = new ArrayList()); + } + list.add(value); + } + + private static ServletChain servletChain(HttpHandler next, final ManagedServlet managedServlet, final String servletPath, final DeploymentInfo deploymentInfo, boolean defaultServlet) { + HttpHandler servletHandler = new ServletSecurityRoleHandler(next, deploymentInfo.getAuthorizationManager()); + servletHandler = wrapHandlers(servletHandler, managedServlet.getServletInfo().getHandlerChainWrappers()); + return new ServletChain(servletHandler, managedServlet, servletPath, defaultServlet); + } + + private static HttpHandler wrapHandlers(final HttpHandler wrapee, final List wrappers) { + HttpHandler current = wrapee; + for (HandlerWrapper wrapper : wrappers) { + current = wrapper.wrap(current); + } + return current; + } + + private static class MatchData { + final ServletHandler handler; + final String matchedPath; + final boolean defaultServlet; + + private MatchData(final ServletHandler handler, final String matchedPath, boolean defaultServlet) { + this.handler = handler; + this.matchedPath = matchedPath; + this.defaultServlet = defaultServlet; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatchesData.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatchesData.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletPathMatchesData.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,163 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers; + +import io.undertow.UndertowMessages; + +import java.util.HashMap; +import java.util.Map; + +/** + * Class that maintains the complete set of servlet path matches. + * + * + * @author Stuart Douglas + */ +class ServletPathMatchesData { + + private final Map exactPathMatches; + + private final Map prefixMatches; + + private final Map nameMatches; + + public ServletPathMatchesData(final Map exactPathMatches, final Map prefixMatches, final Map nameMatches) { + this.prefixMatches = prefixMatches; + this.nameMatches = nameMatches; + Map newExactPathMatches = new HashMap(); + for (Map.Entry entry : exactPathMatches.entrySet()) { + newExactPathMatches.put(entry.getKey(), new ServletPathMatch(entry.getValue(), entry.getKey(), false)); + } + this.exactPathMatches = newExactPathMatches; + + } + + public ServletChain getServletHandlerByName(final String name) { + return nameMatches.get(name); + } + + public ServletPathMatch getServletHandlerByExactPath(final String path) { + return exactPathMatches.get(path); + } + + public ServletPathMatch getServletHandlerByPath(final String path) { + ServletPathMatch exact = exactPathMatches.get(path); + if (exact != null) { + return exact; + } + PathMatch match = prefixMatches.get(path); + if (match != null) { + return handleMatch(path, match, path.lastIndexOf('.')); + } + int extensionPos = -1; + for (int i = path.length() - 1; i >= 0; --i) { + final char c = path.charAt(i); + if (c == '/') { + final String part = path.substring(0, i); + match = prefixMatches.get(part); + if (match != null) { + return handleMatch(path, match, extensionPos); + } + } else if (c == '.') { + if (extensionPos == -1) { + extensionPos = i; + } + } + } + //this should never happen + //as the default servlet is aways registered under /* + throw UndertowMessages.MESSAGES.servletPathMatchFailed(); + } + + private ServletPathMatch handleMatch(final String path, final PathMatch match, final int extensionPos) { + if (match.extensionMatches.isEmpty()) { + return new ServletPathMatch(match.defaultHandler, path, match.requireWelcomeFileMatch); + } else { + if (extensionPos == -1) { + return new ServletPathMatch(match.defaultHandler, path, match.requireWelcomeFileMatch); + } else { + final String ext; + ext = path.substring(extensionPos + 1, path.length()); + ServletChain handler = match.extensionMatches.get(ext); + if (handler != null) { + return new ServletPathMatch(handler, path, handler.getManagedServlet().getServletInfo().isRequireWelcomeFileMapping()); + } else { + return new ServletPathMatch(match.defaultHandler, path, match.requireWelcomeFileMatch); + } + } + } + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + + private final Map exactPathMatches = new HashMap(); + + private final Map prefixMatches = new HashMap(); + + private final Map nameMatches = new HashMap(); + + public void addExactMatch(final String exactMatch, final ServletChain match) { + exactPathMatches.put(exactMatch, match); + } + + public void addPrefixMatch(final String prefix, final ServletChain match, final boolean requireWelcomeFileMatch) { + PathMatch m = prefixMatches.get(prefix); + if (m == null) { + prefixMatches.put(prefix, m = new PathMatch(match)); + } + m.defaultHandler = match; + m.requireWelcomeFileMatch = requireWelcomeFileMatch; + } + + public void addExtensionMatch(final String prefix, final String extension, final ServletChain match) { + PathMatch m = prefixMatches.get(prefix); + if (m == null) { + prefixMatches.put(prefix, m = new PathMatch(null)); + } + m.extensionMatches.put(extension, match); + } + + public void addNameMatch(final String name, final ServletChain match) { + nameMatches.put(name, match); + } + + public ServletPathMatchesData build() { + return new ServletPathMatchesData(exactPathMatches, prefixMatches, nameMatches); + } + + } + + + private static class PathMatch { + + private final Map extensionMatches = new HashMap(); + private volatile ServletChain defaultHandler; + private volatile boolean requireWelcomeFileMatch; + + public PathMatch(final ServletChain defaultHandler) { + this.defaultHandler = defaultHandler; + } + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletRequestContext.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletRequestContext.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/ServletRequestContext.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,214 @@ +package io.undertow.servlet.handlers; + +import java.net.InetSocketAddress; +import java.security.AccessController; +import java.util.List; + +import io.undertow.UndertowMessages; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.ServletStackTraces; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.spec.HttpServletRequestImpl; +import io.undertow.servlet.spec.HttpServletResponseImpl; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.servlet.spec.ServletContextImpl; +import io.undertow.util.AttachmentKey; +import io.undertow.util.Headers; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +/** + * All the information that servlet needs to attach to the exchange. + *

+ * This is all stored under this class, rather than using individual attachments, as + * this approach has significant performance advantages. + *

+ * The {@link ServletInitialHandler} also pushed this information to the {@link #CURRENT} + * thread local, which allows it to be access even if the request or response have been + * wrapped with non-compliant wrapper classes. + * + * @author Stuart Douglas + */ +public class ServletRequestContext { + + private static final RuntimePermission GET_CURRENT_REQUEST = new RuntimePermission("io.undertow.servlet.GET_CURRENT_REQUEST"); + private static final RuntimePermission SET_CURRENT_REQUEST = new RuntimePermission("io.undertow.servlet.SET_CURRENT_REQUEST"); + + private static final ThreadLocal CURRENT = new ThreadLocal(); + + public static void setCurrentRequestContext(ServletRequestContext servletRequestContext) { + if(System.getSecurityManager() != null) { + AccessController.checkPermission(SET_CURRENT_REQUEST); + } + CURRENT.set(servletRequestContext); + } + + public static void clearCurrentServletAttachments() { + if(System.getSecurityManager() != null) { + AccessController.checkPermission(SET_CURRENT_REQUEST); + } + CURRENT.remove(); + } + + public static ServletRequestContext requireCurrent() { + if(System.getSecurityManager() != null) { + AccessController.checkPermission(GET_CURRENT_REQUEST); + } + ServletRequestContext attachments = CURRENT.get(); + if (attachments == null) { + throw UndertowMessages.MESSAGES.noRequestActive(); + } + return attachments; + } + + public static ServletRequestContext current() { + if(System.getSecurityManager() != null) { + AccessController.checkPermission(GET_CURRENT_REQUEST); + } + return CURRENT.get(); + } + + public static final AttachmentKey ATTACHMENT_KEY = AttachmentKey.create(ServletRequestContext.class); + + private final Deployment deployment; + private final HttpServletRequestImpl originalRequest; + private final HttpServletResponseImpl originalResponse; + private final ServletPathMatch originalServletPathMatch; + private ServletResponse servletResponse; + private ServletRequest servletRequest; + private DispatcherType dispatcherType; + + private ServletChain currentServlet; + private ServletPathMatch servletPathMatch; + + private List requiredConstrains; + private TransportGuaranteeType transportGuarenteeType; + private HttpSessionImpl session; + + private ServletContextImpl currentServletContext; + + public ServletRequestContext(final Deployment deployment, final HttpServletRequestImpl originalRequest, final HttpServletResponseImpl originalResponse, final ServletPathMatch originalServletPathMatch) { + this.deployment = deployment; + this.originalRequest = originalRequest; + this.originalResponse = originalResponse; + this.servletRequest = originalRequest; + this.servletResponse = originalResponse; + this.originalServletPathMatch = originalServletPathMatch; + this.currentServletContext = deployment.getServletContext(); + } + + public Deployment getDeployment() { + return deployment; + } + + public ServletChain getCurrentServlet() { + return currentServlet; + } + + public void setCurrentServlet(ServletChain currentServlet) { + this.currentServlet = currentServlet; + } + + public ServletPathMatch getServletPathMatch() { + return servletPathMatch; + } + + public void setServletPathMatch(ServletPathMatch servletPathMatch) { + this.servletPathMatch = servletPathMatch; + } + + public List getRequiredConstrains() { + return requiredConstrains; + } + + public void setRequiredConstrains(List requiredConstrains) { + this.requiredConstrains = requiredConstrains; + } + + public TransportGuaranteeType getTransportGuarenteeType() { + return transportGuarenteeType; + } + + public void setTransportGuarenteeType(TransportGuaranteeType transportGuarenteeType) { + this.transportGuarenteeType = transportGuarenteeType; + } + + public ServletResponse getServletResponse() { + return servletResponse; + } + + public void setServletResponse(ServletResponse servletResponse) { + this.servletResponse = servletResponse; + } + + public ServletRequest getServletRequest() { + return servletRequest; + } + + public void setServletRequest(ServletRequest servletRequest) { + this.servletRequest = servletRequest; + } + + public DispatcherType getDispatcherType() { + return dispatcherType; + } + + public void setDispatcherType(DispatcherType dispatcherType) { + this.dispatcherType = dispatcherType; + } + + public HttpServletRequestImpl getOriginalRequest() { + return originalRequest; + } + + public HttpServletResponseImpl getOriginalResponse() { + return originalResponse; + } + + public HttpSessionImpl getSession() { + return session; + } + + public void setSession(final HttpSessionImpl session) { + this.session = session; + } + + public HttpServerExchange getExchange() { + return originalRequest.getExchange(); + } + + public ServletPathMatch getOriginalServletPathMatch() { + return originalServletPathMatch; + } + + public ServletContextImpl getCurrentServletContext() { + return currentServletContext; + } + + public void setCurrentServletContext(ServletContextImpl currentServletContext) { + this.currentServletContext = currentServletContext; + } + + public boolean displayStackTraces() { + ServletStackTraces mode = deployment.getDeploymentInfo().getServletStackTraces(); + if (mode == ServletStackTraces.NONE) { + return false; + } else if (mode == ServletStackTraces.ALL) { + return true; + } else { + InetSocketAddress localAddress = getExchange().getSourceAddress(); + if(localAddress == null) { + return false; + } + if(!localAddress.getAddress().isLoopbackAddress()) { + return false; + } + return !getExchange().getRequestHeaders().contains(Headers.X_FORWARDED_FOR); + } + + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/SessionRestoringHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/SessionRestoringHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/SessionRestoringHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,155 @@ +package io.undertow.servlet.handlers; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.server.session.SessionManager; +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.api.SessionPersistenceManager; +import io.undertow.servlet.core.Lifecycle; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.servlet.spec.ServletContextImpl; + +import javax.servlet.http.HttpSessionActivationListener; +import javax.servlet.http.HttpSessionEvent; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static io.undertow.servlet.api.SessionPersistenceManager.PersistentSession; + +/** + * A handler that restores persistent HTTP session state for requests in development mode. + *

+ * This handler should not be used in production environments. + * + * @author Stuart Douglas + */ +public class SessionRestoringHandler implements HttpHandler, Lifecycle { + + private final String deploymentName; + private final Map data; + private final SessionManager sessionManager; + private final ServletContextImpl servletContext; + private final HttpHandler next; + private final SessionPersistenceManager sessionPersistenceManager; + private volatile boolean started = false; + + public SessionRestoringHandler(String deploymentName, SessionManager sessionManager, ServletContextImpl servletContext, HttpHandler next, SessionPersistenceManager sessionPersistenceManager) { + this.deploymentName = deploymentName; + this.sessionManager = sessionManager; + this.servletContext = servletContext; + this.next = next; + this.sessionPersistenceManager = sessionPersistenceManager; + this.data = new ConcurrentHashMap(); + } + + public void start() { + ClassLoader old = getTccl(); + try { + setTccl(servletContext.getClassLoader()); + + try { + final Map sessionData = sessionPersistenceManager.loadSessionAttributes(deploymentName, servletContext.getClassLoader()); + if (sessionData != null) { + this.data.putAll(sessionData); + } + } catch (Exception e) { + UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(e); + } + this.started = true; + } finally { + setTccl(old); + } + } + + public void stop() { + ClassLoader old = getTccl(); + try { + setTccl(servletContext.getClassLoader()); + this.started = false; + final Map objectData = new HashMap(); + for (String sessionId : sessionManager.getTransientSessions()) { + Session session = sessionManager.getSession(sessionId); + if (session != null) { + final HttpSessionEvent event = new HttpSessionEvent(SecurityActions.forSession(session, servletContext, false)); + final Map sessionData = new HashMap(); + for (String attr : session.getAttributeNames()) { + final Object attribute = session.getAttribute(attr); + sessionData.put(attr, attribute); + if (attribute instanceof HttpSessionActivationListener) { + ((HttpSessionActivationListener) attribute).sessionWillPassivate(event); + } + } + objectData.put(sessionId, new PersistentSession(new Date(session.getLastAccessedTime() + (session.getMaxInactiveInterval() * 1000)), sessionData)); + } + } + sessionPersistenceManager.persistSessions(deploymentName, objectData); + this.data.clear(); + } finally { + setTccl(old); + } + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + final String incomingSessionId = servletContext.getSessionConfig().findSessionId(exchange); + if (incomingSessionId == null || !data.containsKey(incomingSessionId)) { + next.handleRequest(exchange); + return; + } + + //we have some old data + PersistentSession result = data.remove(incomingSessionId); + if (result != null) { + long time = System.currentTimeMillis(); + if (time < result.getExpiration().getTime()) { + final HttpSessionImpl session = servletContext.getSession(exchange, true); + final HttpSessionEvent event = new HttpSessionEvent(session); + for (Map.Entry entry : result.getSessionData().entrySet()) { + + if (entry.getValue() instanceof HttpSessionActivationListener) { + ((HttpSessionActivationListener) entry.getValue()).sessionWillPassivate(event); + } + session.setAttribute(entry.getKey(), entry.getValue()); + } + } + } + next.handleRequest(exchange); + } + + @Override + public boolean isStarted() { + return started; + } + + private ClassLoader getTccl() { + if (System.getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + } + + private void setTccl(final ClassLoader classLoader) { + if (System.getSecurityManager() == null) { + Thread.currentThread().setContextClassLoader(classLoader); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + Thread.currentThread().setContextClassLoader(classLoader); + return null; + } + }); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/CachedAuthenticatedSessionHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/CachedAuthenticatedSessionHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/CachedAuthenticatedSessionHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,149 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.security.api.AuthenticatedSessionManager; +import io.undertow.security.api.AuthenticatedSessionManager.AuthenticatedSession; +import io.undertow.security.api.NotificationReceiver; +import io.undertow.security.api.SecurityContext; +import io.undertow.security.api.SecurityNotification; +import io.undertow.security.api.SecurityNotification.EventType; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.servlet.spec.ServletContextImpl; +import io.undertow.servlet.util.SavedRequest; + +import javax.servlet.http.HttpSession; +import java.security.AccessController; + +/** + * {@link HttpHandler} responsible for setting up the {@link AuthenticatedSessionManager} for cached authentications and + * registering a {@link NotificationReceiver} to receive the security notifications. + * + * @author Darran Lofthouse + */ +public class CachedAuthenticatedSessionHandler implements HttpHandler { + + private static final String ATTRIBUTE_NAME = CachedAuthenticatedSessionHandler.class.getName() + ".AuthenticatedSession"; + + private final NotificationReceiver NOTIFICATION_RECEIVER = new SecurityNotificationReceiver(); + private final AuthenticatedSessionManager SESSION_MANAGER = new ServletAuthenticatedSessionManager(); + + private final HttpHandler next; + private final ServletContextImpl servletContext; + + public CachedAuthenticatedSessionHandler(final HttpHandler next, final ServletContextImpl servletContext) { + this.next = next; + this.servletContext = servletContext; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + SecurityContext securityContext = exchange.getSecurityContext(); + securityContext.registerNotificationReceiver(NOTIFICATION_RECEIVER); + + HttpSession session = servletContext.getSession(exchange, false); + // If there was no existing HttpSession then there could not be a cached AuthenticatedSession so don't bother setting + // the AuthenticatedSessionManager. + if (session != null) { + exchange.putAttachment(AuthenticatedSessionManager.ATTACHMENT_KEY, SESSION_MANAGER); + SavedRequest.tryRestoreRequest(exchange, session); //not sure if this is where it belongs + } + + next.handleRequest(exchange); + } + + private class SecurityNotificationReceiver implements NotificationReceiver { + + @Override + public void handleNotification(SecurityNotification notification) { + EventType eventType = notification.getEventType(); + switch (eventType) { + case AUTHENTICATED: + if (isCacheable(notification)) { + HttpSessionImpl httpSession = servletContext.getSession(notification.getExchange(), true); + Session session; + if(System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + // It is normal for this notification to be received when using a previously cached session - in that + // case the IDM would have been given an opportunity to re-load the Account so updating here ready for + // the next request is desired. + session.setAttribute(ATTRIBUTE_NAME, + new AuthenticatedSession(notification.getAccount(), notification.getMechanism())); + } + break; + case LOGGED_OUT: + HttpSessionImpl httpSession = servletContext.getSession(notification.getExchange(), false); + if (httpSession != null) { + Session session; + if (System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + session.removeAttribute(ATTRIBUTE_NAME); + } + break; + } + } + + } + + private class ServletAuthenticatedSessionManager implements AuthenticatedSessionManager { + + @Override + public AuthenticatedSession lookupSession(HttpServerExchange exchange) { + HttpSessionImpl httpSession = servletContext.getSession(exchange, false); + if (httpSession != null) { + Session session; + if (System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + return (AuthenticatedSession) session.getAttribute(ATTRIBUTE_NAME); + } + return null; + } + + @Override + public void clearSession(HttpServerExchange exchange) { + HttpSessionImpl httpSession = servletContext.getSession(exchange, false); + if (httpSession != null) { + Session session; + if (System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + session.removeAttribute(ATTRIBUTE_NAME); + } + } + + } + + private boolean isCacheable(final SecurityNotification notification) { + return notification.isProgramatic() || notification.isCachingRequired(); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SSLInformationAssociationHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SSLInformationAssociationHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SSLInformationAssociationHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,116 @@ +package io.undertow.servlet.handlers.security; + +import java.io.ByteArrayInputStream; +import java.security.cert.X509Certificate; +import javax.servlet.ServletRequest; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.SSLSessionInfo; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * Handler that associates SSL metadata with request + *

+ * cipher suite - javax.servlet.request.cipher_suite String + * bit size of the algorithm - javax.servlet.request.key_size Integer + * SSL session id - javax.servlet.request.ssl_session_id String + * + * @author Tomaz Cerar (c) 2013 Red Hat Inc. + */ +public class SSLInformationAssociationHandler implements HttpHandler { + private final HttpHandler next; + + public SSLInformationAssociationHandler(final HttpHandler next) { + this.next = next; + } + + /** + * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream + * cipher key strength. i.e. How much entropy material is in the key material being fed into the + * encryption routines. + *

+ * http://www.thesprawl.org/research/tls-and-ssl-cipher-suites/ + *

+ * + * @param cipherSuite String name of the TLS cipher suite. + * @return int indicating the effective key entropy bit-length. + */ + public static int getKeyLength(String cipherSuite) { + // Roughly ordered from most common to least common. + if (cipherSuite == null) { + return 0; + } else if (cipherSuite.contains("WITH_AES_256_")) { + return 256; + } else if (cipherSuite.contains("WITH_RC4_128_")) { + return 128; + } else if (cipherSuite.contains("WITH_AES_128_")) { + return 128; + } else if (cipherSuite.contains("WITH_RC4_40_")) { + return 40; + } else if (cipherSuite.contains("WITH_3DES_EDE_CBC_")) { + return 168; + } else if (cipherSuite.contains("WITH_IDEA_CBC_")) { + return 128; + } else if (cipherSuite.contains("WITH_RC2_CBC_40_")) { + return 40; + } else if (cipherSuite.contains("WITH_DES40_CBC_")) { + return 40; + } else if (cipherSuite.contains("WITH_DES_CBC_")) { + return 56; + } else { + return 0; + } + } + + + /* ------------------------------------------------------------ */ + + /** + *

Return the chain of X509 certificates used to negotiate the SSL Session.

+ *

+ * We convert JSSE's javax.security.cert.X509Certificate[] to servlet's java.security.cert.X509Certificate[] + * + * @param session the javax.net.ssl.SSLSession to use as the source of the cert chain. + * @return the chain of java.security.cert.X509Certificates used to + * negotiate the SSL connection.
+ * Will be null if the chain is missing or empty. + */ + private X509Certificate[] getCerts(SSLSessionInfo session) { + try { + javax.security.cert.X509Certificate[] javaxCerts = session.getPeerCertificateChain(); + if (javaxCerts == null || javaxCerts.length == 0) { + return null; + } + X509Certificate[] javaCerts = new X509Certificate[javaxCerts.length]; + java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509"); + for (int i = 0; i < javaxCerts.length; i++) { + byte[] bytes = javaxCerts[i].getEncoded(); + ByteArrayInputStream stream = new ByteArrayInputStream(bytes); + javaCerts[i] = (X509Certificate) cf.generateCertificate(stream); + } + + return javaCerts; + } catch (Exception e) { + return null; + } + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + ServletRequest request = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletRequest(); + SSLSessionInfo ssl = exchange.getConnection().getSslSessionInfo(); + if (ssl != null) { + request.setAttribute("javax.servlet.request.cipher_suite", ssl.getCipherSuite()); + request.setAttribute("javax.servlet.request.key_size", getKeyLength(ssl.getCipherSuite())); + request.setAttribute("javax.servlet.request.ssl_session_id", ssl.getSessionId()); + X509Certificate[] certs = getCerts(ssl); + if (certs != null) { + request.setAttribute("javax.servlet.request.X509Certificate", certs); + } + + } + next.handleRequest(exchange); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatch.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatch.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatch.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.api.TransportGuaranteeType; + +/** + * @author Stuart Douglas + */ +class SecurityPathMatch { + + private final TransportGuaranteeType transportGuaranteeType; + private final SingleConstraintMatch mergedConstraint; + + SecurityPathMatch(final TransportGuaranteeType transportGuaranteeType, final SingleConstraintMatch mergedConstraint) { + this.transportGuaranteeType = transportGuaranteeType; + this.mergedConstraint = mergedConstraint; + } + + TransportGuaranteeType getTransportGuaranteeType() { + return transportGuaranteeType; + } + + SingleConstraintMatch getMergedConstraint() { + return mergedConstraint; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatches.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatches.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/SecurityPathMatches.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,271 @@ +package io.undertow.servlet.handlers.security; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.SecurityInfo; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.api.WebResourceCollection; + +/** + * @author Stuart Douglas + */ +public class SecurityPathMatches { + + private final boolean denyUncoveredHttpMethods; + private final PathSecurityInformation defaultPathSecurityInformation; + private final Map exactPathRoleInformation; + private final Map prefixPathRoleInformation; + private final Map extensionRoleInformation; + + private SecurityPathMatches(final boolean denyUncoveredHttpMethods, final PathSecurityInformation defaultPathSecurityInformation, final Map exactPathRoleInformation, final Map prefixPathRoleInformation, final Map extensionRoleInformation) { + this.denyUncoveredHttpMethods = denyUncoveredHttpMethods; + this.defaultPathSecurityInformation = defaultPathSecurityInformation; + this.exactPathRoleInformation = exactPathRoleInformation; + this.prefixPathRoleInformation = prefixPathRoleInformation; + this.extensionRoleInformation = extensionRoleInformation; + } + + /** + * + * @return true If no security path information has been defined + */ + public boolean isEmpty() { + return defaultPathSecurityInformation.excludedMethodRoles.isEmpty() && + defaultPathSecurityInformation.perMethodRequiredRoles.isEmpty() && + defaultPathSecurityInformation.defaultRequiredRoles.isEmpty() && + exactPathRoleInformation.isEmpty() && + prefixPathRoleInformation.isEmpty() && + extensionRoleInformation.isEmpty(); + } + + public SecurityPathMatch getSecurityInfo(final String path, final String method) { + RuntimeMatch currentMatch = new RuntimeMatch(); + handleMatch(method, defaultPathSecurityInformation, currentMatch); + PathSecurityInformation match = exactPathRoleInformation.get(path); + if (match != null) { + handleMatch(method, match, currentMatch); + } + + match = prefixPathRoleInformation.get(path); + if (match != null) { + handleMatch(method, match, currentMatch); + } + + int qsPos = -1; + boolean extension = false; + for (int i = path.length() - 1; i >= 0; --i) { + final char c = path.charAt(i); + if (c == '?') { + //there was a query string, check the exact matches again + final String part = path.substring(0, i); + match = exactPathRoleInformation.get(part); + if (match != null) { + handleMatch(method, match, currentMatch); + } + qsPos = i; + extension = false; + } else if (c == '/') { + extension = true; + final String part = path.substring(0, i); + match = prefixPathRoleInformation.get(part); + if (match != null) { + handleMatch(method, match, currentMatch); + } + } else if (c == '.') { + if (!extension) { + extension = true; + final String ext; + if (qsPos == -1) { + ext = path.substring(i + 1, path.length()); + } else { + ext = path.substring(i + 1, qsPos); + } + match = extensionRoleInformation.get(ext); + if (match != null) { + handleMatch(method, match, currentMatch); + } + } + } + } + + + return new SecurityPathMatch(currentMatch.type, mergeConstraints(currentMatch)); + } + + /** + * merge all constraints, as per 13.8.1 Combining Constraints + * @param constraintSet + */ + private SingleConstraintMatch mergeConstraints(final RuntimeMatch currentMatch) { + if(currentMatch.uncovered && denyUncoveredHttpMethods) { + return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.DENY, Collections.emptySet()); + } + final Set allowedRoles = new HashSet(); + for(SingleConstraintMatch match : currentMatch.constraints) { + if(match.getRequiredRoles().isEmpty()) { + return new SingleConstraintMatch(match.getEmptyRoleSemantic(), Collections.emptySet()); + } else { + allowedRoles.addAll(match.getRequiredRoles()); + } + } + return new SingleConstraintMatch(SecurityInfo.EmptyRoleSemantic.PERMIT, allowedRoles); + } + + private void handleMatch(final String method, final PathSecurityInformation exact, RuntimeMatch currentMatch) { + List roles = exact.defaultRequiredRoles; + for (SecurityInformation role : roles) { + transport(currentMatch, role.transportGuaranteeType); + currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles)); + if(role.emptyRoleSemantic == SecurityInfo.EmptyRoleSemantic.DENY || !role.roles.isEmpty()) { + currentMatch.uncovered = false; + } + } + List methodInfo = exact.perMethodRequiredRoles.get(method); + if (methodInfo != null) { + currentMatch.uncovered = false; + for (SecurityInformation role : methodInfo) { + transport(currentMatch, role.transportGuaranteeType); + currentMatch.constraints.add(new SingleConstraintMatch(role.emptyRoleSemantic, role.roles)); + } + } + for (ExcludedMethodRoles excluded : exact.excludedMethodRoles) { + if (!excluded.methods.contains(method)) { + currentMatch.uncovered = false; + transport(currentMatch, excluded.securityInformation.transportGuaranteeType); + currentMatch.constraints.add(new SingleConstraintMatch(excluded.securityInformation.emptyRoleSemantic, excluded.securityInformation.roles)); + } + } + } + + private void transport(RuntimeMatch match, TransportGuaranteeType other) { + if (other.ordinal() > match.type.ordinal()) { + match.type = other; + } + } + + public static Builder builder(final DeploymentInfo deploymentInfo) { + return new Builder(deploymentInfo); + } + + public static class Builder { + private final DeploymentInfo deploymentInfo; + private final PathSecurityInformation defaultPathSecurityInformation = new PathSecurityInformation(); + private final Map exactPathRoleInformation = new HashMap(); + private final Map prefixPathRoleInformation = new HashMap(); + private final Map extensionRoleInformation = new HashMap(); + + private Builder(final DeploymentInfo deploymentInfo) { + this.deploymentInfo = deploymentInfo; + } + + public void addSecurityConstraint(final SecurityConstraint securityConstraint) { + final Set roles = expandRolesAllowed(securityConstraint.getRolesAllowed()); + final SecurityInformation securityInformation = new SecurityInformation(roles, securityConstraint.getTransportGuaranteeType(), securityConstraint.getEmptyRoleSemantic()); + for (final WebResourceCollection webResources : securityConstraint.getWebResourceCollections()) { + if (webResources.getUrlPatterns().isEmpty()) { + //default that is applied to everything + setupPathSecurityInformation(defaultPathSecurityInformation, securityInformation, webResources); + } + for (String pattern : webResources.getUrlPatterns()) { + if (pattern.endsWith("/*") || pattern.endsWith("/")) { + String part = pattern.substring(0, pattern.lastIndexOf('/')); + PathSecurityInformation info = prefixPathRoleInformation.get(part); + if (info == null) { + prefixPathRoleInformation.put(part, info = new PathSecurityInformation()); + } + setupPathSecurityInformation(info, securityInformation, webResources); + } else if (pattern.startsWith("*.")) { + String part = pattern.substring(2, pattern.length()); + PathSecurityInformation info = extensionRoleInformation.get(part); + if (info == null) { + extensionRoleInformation.put(part, info = new PathSecurityInformation()); + } + setupPathSecurityInformation(info, securityInformation, webResources); + } else { + PathSecurityInformation info = exactPathRoleInformation.get(pattern); + if (info == null) { + exactPathRoleInformation.put(pattern, info = new PathSecurityInformation()); + } + setupPathSecurityInformation(info, securityInformation, webResources); + } + } + } + + } + + private Set expandRolesAllowed(final Set rolesAllowed) { + final Set roles = new HashSet(rolesAllowed); + if (roles.contains("*")) { + roles.remove("*"); + roles.addAll(deploymentInfo.getSecurityRoles()); + } + + return roles; + } + + private void setupPathSecurityInformation(final PathSecurityInformation info, final SecurityInformation securityConstraint, final WebResourceCollection webResources) { + if (webResources.getHttpMethods().isEmpty() && + webResources.getHttpMethodOmissions().isEmpty()) { + info.defaultRequiredRoles.add(securityConstraint); + } else if (!webResources.getHttpMethods().isEmpty()) { + for (String method : webResources.getHttpMethods()) { + List securityInformations = info.perMethodRequiredRoles.get(method); + if (securityInformations == null) { + info.perMethodRequiredRoles.put(method, securityInformations = new ArrayList()); + } + securityInformations.add(securityConstraint); + } + } else if (!webResources.getHttpMethodOmissions().isEmpty()) { + info.excludedMethodRoles.add(new ExcludedMethodRoles(webResources.getHttpMethodOmissions(), securityConstraint)); + } + } + + public SecurityPathMatches build() { + return new SecurityPathMatches(deploymentInfo.isDenyUncoveredHttpMethods(), defaultPathSecurityInformation, exactPathRoleInformation, prefixPathRoleInformation, extensionRoleInformation); + } + } + + + private static class PathSecurityInformation { + final List defaultRequiredRoles = new ArrayList(); + final Map> perMethodRequiredRoles = new HashMap>(); + final List excludedMethodRoles = new ArrayList(); + } + + private static final class ExcludedMethodRoles { + final Set methods; + final SecurityInformation securityInformation; + + public ExcludedMethodRoles(final Set methods, final SecurityInformation securityInformation) { + this.methods = methods; + this.securityInformation = securityInformation; + } + } + + private static final class SecurityInformation { + final Set roles; + final TransportGuaranteeType transportGuaranteeType; + final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic; + + private SecurityInformation(final Set roles, final TransportGuaranteeType transportGuaranteeType, final SecurityInfo.EmptyRoleSemantic emptyRoleSemantic) { + this.emptyRoleSemantic = emptyRoleSemantic; + this.roles = new HashSet(roles); + this.transportGuaranteeType = transportGuaranteeType; + } + } + + private static final class RuntimeMatch { + TransportGuaranteeType type = TransportGuaranteeType.NONE; + final List constraints = new ArrayList(); + boolean uncovered = true; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationCallHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationCallHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationCallHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,68 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.security.api.SecurityContext; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * This is the final {@link io.undertow.server.HttpHandler} in the security chain, it's purpose is to act as a barrier at the end of the chain to + * ensure authenticate is called after the mechanisms have been associated with the context and the constraint checked. + * + * This handler uses the Servlet {@link javax.servlet.http.HttpServletResponse#sendError(int)} method to make + * sure the correct error page is displayed. + * + * @author Darran Lofthouse + */ +public class ServletAuthenticationCallHandler implements HttpHandler { + + private final HttpHandler next; + + public ServletAuthenticationCallHandler(final HttpHandler next) { + this.next = next; + } + + /** + * Only allow the request through if successfully authenticated or if authentication is not required. + * + * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange) + */ + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + if(exchange.isInIoThread()) { + exchange.dispatch(this); + return; + } + SecurityContext context = exchange.getSecurityContext(); + if (context.authenticate()) { + if(!exchange.isComplete()) { + next.handleRequest(exchange); + } + } else { + if(exchange.getResponseCode() >= 400 && !exchange.isComplete()) { + ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + src.getOriginalResponse().sendError(exchange.getResponseCode()); + } else { + exchange.endExchange(); + } + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationConstraintHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationConstraintHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletAuthenticationConstraintHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,69 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import java.util.List; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.security.handlers.AuthenticationConstraintHandler; +import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * A simple handler that just sets the auth type to REQUIRED after iterating each of the {@link SingleConstraintMatch} instances + * and identifying if any require authentication. + * + * @author Stuart Douglas + * @author Darran Lofthouse + */ +public class ServletAuthenticationConstraintHandler extends AuthenticationConstraintHandler { + + public ServletAuthenticationConstraintHandler(final HttpHandler next) { + super(next); + } + + @Override + protected boolean isAuthenticationRequired(final HttpServerExchange exchange) { + List constraints = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getRequiredConstrains(); + + /* + * Even once this is set to true the reason we allow the loop to continue is in case an empty role with a semantic of + * deny is found as that will override everything. + */ + boolean authenticationRequired = false; + for (SingleConstraintMatch constraint : constraints) { + if (constraint.getRequiredRoles().isEmpty()) { + if (constraint.getEmptyRoleSemantic() == EmptyRoleSemantic.DENY) { + /* + * For this case we return false as we know it can never be satisfied. + */ + return false; + } else if (constraint.getEmptyRoleSemantic() == EmptyRoleSemantic.AUTHENTICATE) { + authenticationRequired = true; + } + } else { + authenticationRequired = true; + } + } + + return authenticationRequired; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletConfidentialityConstraintHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletConfidentialityConstraintHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletConfidentialityConstraintHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,78 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.security.handlers.SinglePortConfidentialityHandler; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.AuthorizationManager; +import io.undertow.servlet.api.ConfidentialPortManager; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.handlers.ServletRequestContext; + +import javax.servlet.http.HttpServletResponse; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Servlet specific extension to {@see SinglePortConfidentialityHandler} + * + * @author Darran Lofthouse + */ +public class ServletConfidentialityConstraintHandler extends SinglePortConfidentialityHandler { + + private final ConfidentialPortManager portManager; + + public ServletConfidentialityConstraintHandler(final ConfidentialPortManager portManager, final HttpHandler next) { + super(next, -1); + this.portManager = portManager; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + final AuthorizationManager authorizationManager = servletRequestContext.getDeployment().getDeploymentInfo().getAuthorizationManager(); + + TransportGuaranteeType connectionGuarantee = servletRequestContext.getOriginalRequest().isSecure() ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE; + TransportGuaranteeType transportGuarantee = authorizationManager.transportGuarantee(connectionGuarantee, + servletRequestContext.getTransportGuarenteeType(), servletRequestContext.getOriginalRequest()); + servletRequestContext.setTransportGuarenteeType(transportGuarantee); + + if (TransportGuaranteeType.REJECTED == transportGuarantee) { + HttpServletResponse response = (HttpServletResponse) servletRequestContext.getServletResponse(); + response.sendError(403); + return; + } + super.handleRequest(exchange); + } + + @Override + protected boolean confidentialityRequired(HttpServerExchange exchange) { + TransportGuaranteeType transportGuarantee = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getTransportGuarenteeType(); + + // TODO - We may be able to add more flexibility here especially with authentication mechanisms such as Digest for + // INTEGRAL - for now just use SSL. + return (TransportGuaranteeType.CONFIDENTIAL == transportGuarantee || TransportGuaranteeType.INTEGRAL == transportGuarantee); + } + + @Override + protected URI getRedirectURI(HttpServerExchange exchange) throws URISyntaxException { + return super.getRedirectURI(exchange, portManager.getConfidentialPort(exchange)); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletFormAuthenticationMechanism.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletFormAuthenticationMechanism.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletFormAuthenticationMechanism.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,120 @@ +package io.undertow.servlet.handlers.security; + +import io.undertow.security.api.AuthenticationMechanism; +import io.undertow.security.api.AuthenticationMechanismFactory; +import io.undertow.security.impl.FormAuthenticationMechanism; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormParserFactory; +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.servlet.util.SavedRequest; +import io.undertow.util.Headers; +import io.undertow.util.RedirectBuilder; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.AccessController; +import java.util.Map; + +/** + * Servlet handler for FORM authentication. Instead of using a redirect it + * serves up error and login pages immediately using a forward + * + * @author Stuart Douglas + */ +public class ServletFormAuthenticationMechanism extends FormAuthenticationMechanism { + + private static final String SESSION_KEY = "io.undertow.servlet.form.auth.redirect.location"; + + public static final Factory FACTORY = new Factory(); + + @Deprecated + public ServletFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage) { + super(name, loginPage, errorPage); + } + + @Deprecated + public ServletFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage, final String postLocation) { + super(name, loginPage, errorPage, postLocation); + } + + public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage, String postLocation) { + super(formParserFactory, name, loginPage, errorPage, postLocation); + } + + public ServletFormAuthenticationMechanism(FormParserFactory formParserFactory, String name, String loginPage, String errorPage) { + super(formParserFactory, name, loginPage, errorPage); + } + + @Override + protected Integer servePage(final HttpServerExchange exchange, final String location) { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + ServletRequest req = servletRequestContext.getServletRequest(); + ServletResponse resp = servletRequestContext.getServletResponse(); + RequestDispatcher disp = req.getRequestDispatcher(location); + //make sure the login page is never cached + exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); + exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache"); + exchange.getResponseHeaders().add(Headers.EXPIRES, "0"); + + + try { + disp.forward(req, resp); + } catch (ServletException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + protected void storeInitialLocation(final HttpServerExchange exchange) { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + HttpSessionImpl httpSession = servletRequestContext.getCurrentServletContext().getSession(exchange, true); + Session session; + if (System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + session.setAttribute(SESSION_KEY, RedirectBuilder.redirect(exchange, exchange.getRelativePath())); + SavedRequest.trySaveRequest(exchange); + } + + @Override + protected void handleRedirectBack(final HttpServerExchange exchange) { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + HttpServletResponse resp = (HttpServletResponse) servletRequestContext.getServletResponse(); + HttpSessionImpl httpSession = servletRequestContext.getCurrentServletContext().getSession(exchange, false); + if (httpSession != null) { + Session session; + if (System.getSecurityManager() == null) { + session = httpSession.getSession(); + } else { + session = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + String path = (String) session.getAttribute(SESSION_KEY); + if (path != null) { + try { + resp.sendRedirect(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + } + + public static class Factory implements AuthenticationMechanismFactory { + @Override + public AuthenticationMechanism create(String mechanismName, FormParserFactory formParserFactory, Map properties) { + return new ServletFormAuthenticationMechanism(formParserFactory, mechanismName, properties.get(LOGIN_PAGE), properties.get(ERROR_PAGE)); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityConstraintHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityConstraintHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityConstraintHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,58 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.handlers.ServletRequestContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Stuart Douglas + */ +public class ServletSecurityConstraintHandler implements HttpHandler { + + private final SecurityPathMatches securityPathMatches; + private final HttpHandler next; + + public ServletSecurityConstraintHandler(final SecurityPathMatches securityPathMatches, final HttpHandler next) { + this.securityPathMatches = securityPathMatches; + this.next = next; + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + final String path = exchange.getRelativePath(); + SecurityPathMatch securityMatch = securityPathMatches.getSecurityInfo(path, exchange.getRequestMethod().toString()); + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + List list = servletRequestContext.getRequiredConstrains(); + if (list == null) { + servletRequestContext.setRequiredConstrains(list = new ArrayList()); + } + list.add(securityMatch.getMergedConstraint()); + TransportGuaranteeType type = servletRequestContext.getTransportGuarenteeType(); + if (type == null || type.ordinal() < securityMatch.getTransportGuaranteeType().ordinal()) { + servletRequestContext.setTransportGuarenteeType(securityMatch.getTransportGuaranteeType()); + } + next.handleRequest(exchange); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityRoleHandler.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityRoleHandler.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSecurityRoleHandler.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,65 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.handlers.security; + +import io.undertow.security.api.SecurityContext; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.api.AuthorizationManager; +import io.undertow.servlet.api.SingleConstraintMatch; +import io.undertow.servlet.handlers.ServletRequestContext; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * Servlet role handler + * + * @author Stuart Douglas + */ +public class ServletSecurityRoleHandler implements HttpHandler { + + private final HttpHandler next; + private final AuthorizationManager authorizationManager; + + public ServletSecurityRoleHandler(final HttpHandler next, AuthorizationManager authorizationManager) { + this.next = next; + this.authorizationManager = authorizationManager; + } + + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + ServletRequest request = servletRequestContext.getServletRequest(); + if (request.getDispatcherType() == DispatcherType.REQUEST) { + List constraints = servletRequestContext.getRequiredConstrains(); + SecurityContext sc = exchange.getSecurityContext(); + if (!authorizationManager.canAccessResource(constraints, sc.getAuthenticatedAccount(), servletRequestContext.getCurrentServlet().getManagedServlet().getServletInfo(), servletRequestContext.getOriginalRequest(), servletRequestContext.getDeployment())) { + + HttpServletResponse response = (HttpServletResponse) servletRequestContext.getServletResponse(); + response.sendError(403); + return; + } + } + next.handleRequest(exchange); + } + + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSingleSignOnAuthenticationMechainism.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSingleSignOnAuthenticationMechainism.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/handlers/security/ServletSingleSignOnAuthenticationMechainism.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,32 @@ +package io.undertow.servlet.handlers.security; + +import io.undertow.security.impl.SingleSignOnAuthenticationMechanism; +import io.undertow.security.impl.SingleSignOnManager; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; + +import java.security.AccessController; + +/** + * Servlet version of the single sign on authentication mechanism. + * + * @author Stuart Douglas + */ +public class ServletSingleSignOnAuthenticationMechainism extends SingleSignOnAuthenticationMechanism { + public ServletSingleSignOnAuthenticationMechainism(SingleSignOnManager storage) { + super(storage); + } + + @Override + protected Session getSession(HttpServerExchange exchange) { + ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + final HttpSessionImpl session = servletRequestContext.getCurrentServletContext().getSession(exchange, true); + if(System.getSecurityManager() == null) { + return session.getSession(); + } else { + return AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/predicate/DispatcherTypePredicate.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/predicate/DispatcherTypePredicate.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/predicate/DispatcherTypePredicate.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,74 @@ +package io.undertow.servlet.predicate; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.DispatcherType; + +import io.undertow.predicate.Predicate; +import io.undertow.predicate.PredicateBuilder; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.handlers.ServletRequestContext; + +/** + * Predicate that returns true if the dispatcher type matches the specified type. + * + * @author Stuart Douglas + */ +public class DispatcherTypePredicate implements Predicate { + + public static final DispatcherTypePredicate FORWARD = new DispatcherTypePredicate(DispatcherType.FORWARD); + public static final DispatcherTypePredicate INCLUDE = new DispatcherTypePredicate(DispatcherType.INCLUDE); + public static final DispatcherTypePredicate REQUEST = new DispatcherTypePredicate(DispatcherType.REQUEST); + public static final DispatcherTypePredicate ASYNC = new DispatcherTypePredicate(DispatcherType.ASYNC); + public static final DispatcherTypePredicate ERROR = new DispatcherTypePredicate(DispatcherType.ERROR); + + + private final DispatcherType dispatcherType; + + public DispatcherTypePredicate(final DispatcherType dispatcherType) { + this.dispatcherType = dispatcherType; + } + + @Override + public boolean resolve(final HttpServerExchange value) { + return value.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType() == dispatcherType; + } + + + public static class Builder implements PredicateBuilder { + + @Override + public String name() { + return "dispatcher"; + } + + @Override + public Map> parameters() { + final Map> params = new HashMap>(); + params.put("value", String.class); + return params; + } + + @Override + public Set requiredParameters() { + final Set params = new HashSet(); + params.add("value"); + return params; + } + + @Override + public String defaultParameter() { + return "value"; + } + + @Override + public Predicate build(final Map config) { + String value = (String) config.get("value"); + return new DispatcherTypePredicate(DispatcherType.valueOf(value)); + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/AsyncContextImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/AsyncContextImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/AsyncContextImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,663 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.undertow.UndertowLogger; +import io.undertow.server.Connectors; +import io.undertow.server.ExchangeCompletionListener; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletDispatcher; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.core.CompositeThreadSetupAction; +import io.undertow.servlet.handlers.ServletDebugPageHandler; +import io.undertow.servlet.handlers.ServletPathMatch; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.util.AttachmentKey; +import io.undertow.util.CanonicalPathUtils; +import io.undertow.util.SameThreadExecutor; +import org.xnio.IoUtils; +import org.xnio.XnioExecutor; + +/** + * @author Stuart Douglas + */ +public class AsyncContextImpl implements AsyncContext { + + public static final AttachmentKey ASYNC_SUPPORTED = AttachmentKey.create(Boolean.class); + + private final List asyncListeners = new CopyOnWriteArrayList(); + + private final HttpServerExchange exchange; + private final ServletRequest servletRequest; + private final ServletResponse servletResponse; + private final TimeoutTask timeoutTask = new TimeoutTask(); + private final ServletRequestContext servletRequestContext; + private final boolean requestSupplied; + + private AsyncContextImpl previousAsyncContext; //the previous async context + + + //todo: make default configurable + private volatile long timeout = 30000; + + private volatile XnioExecutor.Key timeoutKey; + + private boolean dispatched; + private boolean initialRequestDone; + private Thread initiatingThread; + + private final Deque asyncTaskQueue = new ArrayDeque(); + private boolean processingAsyncTask = false; + private boolean complete = false; + + public AsyncContextImpl(final HttpServerExchange exchange, final ServletRequest servletRequest, final ServletResponse servletResponse, final ServletRequestContext servletRequestContext, boolean requestSupplied, final AsyncContextImpl previousAsyncContext) { + this.exchange = exchange; + this.servletRequest = servletRequest; + this.servletResponse = servletResponse; + this.servletRequestContext = servletRequestContext; + this.requestSupplied = requestSupplied; + this.previousAsyncContext = previousAsyncContext; + initiatingThread = Thread.currentThread(); + exchange.dispatch(SameThreadExecutor.INSTANCE, new Runnable() { + @Override + public void run() { + exchange.setDispatchExecutor(null); + initialRequestDone(); + } + }); + } + + public void updateTimeout() { + XnioExecutor.Key key = this.timeoutKey; + if (key != null) { + if (!key.remove()) { + return; + } + } + if (timeout > 0) { + this.timeoutKey = exchange.getIoThread().executeAfter(timeoutTask, timeout, TimeUnit.MILLISECONDS); + } + } + + @Override + public ServletRequest getRequest() { + return servletRequest; + } + + @Override + public ServletResponse getResponse() { + return servletResponse; + } + + @Override + public boolean hasOriginalRequestAndResponse() { + return servletRequest instanceof HttpServletRequestImpl && + servletResponse instanceof HttpServletResponseImpl; + } + + @Override + public void dispatch() { + if (dispatched) { + throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); + } + final HttpServletRequestImpl requestImpl = this.servletRequestContext.getOriginalRequest(); + Deployment deployment = requestImpl.getServletContext().getDeployment(); + + if (requestSupplied && servletRequest instanceof HttpServletRequest) { + ServletContainer container = deployment.getServletContainer(); + final String requestURI = ((HttpServletRequest) servletRequest).getRequestURI(); + DeploymentManager context = container.getDeploymentByPath(requestURI); + if (context == null) { + throw UndertowServletMessages.MESSAGES.couldNotFindContextToDispatchTo(requestImpl.getOriginalContextPath()); + } + String toDispatch = requestURI.substring(context.getDeployment().getServletContext().getContextPath().length()); + String qs = ((HttpServletRequest) servletRequest).getQueryString(); + if (qs != null && !qs.isEmpty()) { + toDispatch = toDispatch + "?" + qs; + } + dispatch(context.getDeployment().getServletContext(), toDispatch); + + } else { + //original request + ServletContainer container = deployment.getServletContainer(); + DeploymentManager context = container.getDeploymentByPath(requestImpl.getOriginalContextPath()); + if (context == null) { + //this should never happen + throw UndertowServletMessages.MESSAGES.couldNotFindContextToDispatchTo(requestImpl.getOriginalContextPath()); + } + String toDispatch = CanonicalPathUtils.canonicalize(requestImpl.getOriginalRequestURI()).substring(requestImpl.getOriginalContextPath().length()); + String qs = requestImpl.getOriginalQueryString(); + if (qs != null && !qs.isEmpty()) { + toDispatch = toDispatch + "?" + qs; + } + dispatch(context.getDeployment().getServletContext(), toDispatch); + } + } + + private void dispatchAsyncRequest(final ServletDispatcher servletDispatcher, final ServletPathMatch pathInfo, final HttpServerExchange exchange) { + doDispatch(new Runnable() { + @Override + public void run() { + Connectors.executeRootHandler(new HttpHandler() { + @Override + public void handleRequest(final HttpServerExchange exchange) throws Exception { + servletDispatcher.dispatchToPath(exchange, pathInfo, DispatcherType.ASYNC); + } + }, exchange); + } + }); + } + + @Override + public void dispatch(final String path) { + dispatch(servletRequest.getServletContext(), path); + } + + @Override + public void dispatch(final ServletContext context, final String path) { + + if (dispatched) { + throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); + } + + HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); + HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); + final HttpServerExchange exchange = requestImpl.getExchange(); + + exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).setDispatcherType(DispatcherType.ASYNC); + + requestImpl.setAttribute(ASYNC_REQUEST_URI, requestImpl.getOriginalRequestURI()); + requestImpl.setAttribute(ASYNC_CONTEXT_PATH, requestImpl.getOriginalContextPath()); + requestImpl.setAttribute(ASYNC_SERVLET_PATH, requestImpl.getOriginalServletPath()); + requestImpl.setAttribute(ASYNC_QUERY_STRING, requestImpl.getOriginalQueryString()); + + String newQueryString = ""; + int qsPos = path.indexOf("?"); + String newServletPath = path; + if (qsPos != -1) { + newQueryString = newServletPath.substring(qsPos + 1); + newServletPath = newServletPath.substring(0, qsPos); + } + String newRequestUri = context.getContextPath() + newServletPath; + + //todo: a more efficient impl + Map> newQueryParameters = new HashMap>(); + for (String part : newQueryString.split("&")) { + String name = part; + String value = ""; + int equals = part.indexOf('='); + if (equals != -1) { + name = part.substring(0, equals); + value = part.substring(equals + 1); + } + Deque queue = newQueryParameters.get(name); + if (queue == null) { + newQueryParameters.put(name, queue = new ArrayDeque(1)); + } + queue.add(value); + } + requestImpl.setQueryParameters(newQueryParameters); + + requestImpl.getExchange().setRelativePath(newServletPath); + requestImpl.getExchange().setQueryString(newQueryString); + requestImpl.getExchange().setRequestPath(newRequestUri); + requestImpl.getExchange().setRequestURI(newRequestUri); + requestImpl.setServletContext((ServletContextImpl) context); + responseImpl.setServletContext((ServletContextImpl) context); + + Deployment deployment = requestImpl.getServletContext().getDeployment(); + ServletPathMatch info = deployment.getServletPaths().getServletHandlerByPath(newServletPath); + requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(info); + + dispatchAsyncRequest(deployment.getServletDispatcher(), info, exchange); + } + + @Override + public synchronized void complete() { + if(complete) { + UndertowLogger.REQUEST_LOGGER.trace("Ignoring call to AsyncContext.complete() as it has already been called"); + return; + } + complete = true; + onAsyncComplete(); + if(!dispatched) { + completeInternal(); + } + if(previousAsyncContext != null) { + previousAsyncContext.complete(); + } + } + + public synchronized void completeInternal() { + + if (!initialRequestDone && Thread.currentThread() == initiatingThread) { + //the context was stopped in the same request context it was started, we don't do anything + if (dispatched) { + throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); + } + exchange.unDispatch(); + dispatched = true; + initialRequestDone(); + } else { + doDispatch(new Runnable() { + @Override + public void run() { + //we do not run the ServletRequestListeners here, as the request does not come into the scope + //of a web application, as defined by the javadoc on ServletRequestListener + HttpServletResponseImpl response = servletRequestContext.getOriginalResponse(); + response.responseDone(); + } + }); + } + } + + @Override + public void start(final Runnable run) { + Executor executor = asyncExecutor(); + final CompositeThreadSetupAction setup = servletRequestContext.getDeployment().getThreadSetupAction(); + executor.execute(new Runnable() { + @Override + public void run() { + ThreadSetupAction.Handle handle = setup.setup(null); + try { + run.run(); + } finally { + handle.tearDown(); + } + } + }); + + } + + private Executor asyncExecutor() { + Executor executor = servletRequestContext.getDeployment().getAsyncExecutor(); + if (executor == null) { + executor = servletRequestContext.getDeployment().getExecutor(); + } + if (executor == null) { + executor = exchange.getConnection().getWorker(); + } + return executor; + } + + + @Override + public void addListener(final AsyncListener listener) { + asyncListeners.add(new BoundAsyncListener(listener, servletRequest, servletResponse)); + } + + @Override + public void addListener(final AsyncListener listener, final ServletRequest servletRequest, final ServletResponse servletResponse) { + asyncListeners.add(new BoundAsyncListener(listener, servletRequest, servletResponse)); + } + + public boolean isDispatched() { + return dispatched; + } + + @Override + public T createListener(final Class clazz) throws ServletException { + try { + InstanceFactory factory = ((ServletContextImpl) this.servletRequest.getServletContext()).getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(clazz); + + final InstanceHandle instance = factory.createInstance(); + exchange.addExchangeCompleteListener(new ExchangeCompletionListener() { + @Override + public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) { + try { + instance.release(); + } finally { + nextListener.proceed(); + } + } + }); + return instance.getInstance(); + } catch (Exception e) { + throw new ServletException(e); + } + } + + @Override + public void setTimeout(final long timeout) { + if (initialRequestDone) { + throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyReturnedToContainer(); + } + this.timeout = timeout; + } + + @Override + public long getTimeout() { + return timeout; + } + + public void handleError(final Throwable error) { + dispatched = false; //we reset the dispatched state + onAsyncError(error); + if (!dispatched) { + servletRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION, error); + try { + boolean errorPage = servletRequestContext.displayStackTraces(); + if(errorPage) { + ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, error); + } else { + if (servletResponse instanceof HttpServletResponse) { + ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } else { + servletRequestContext.getOriginalResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + } catch (IOException e) { + UndertowLogger.REQUEST_IO_LOGGER.ioException(e); + } + if (!dispatched) { + complete(); + } + } + } + + /** + * Called by the container when the initial request is finished. + * If this request has a dispatch or complete call pending then + * this will be started. + */ + public synchronized void initialRequestDone() { + initialRequestDone = true; + if (previousAsyncContext != null) { + previousAsyncContext.onAsyncStart(this); + previousAsyncContext = null; + } + if (!processingAsyncTask) { + processAsyncTask(); + } + initiatingThread = null; + } + + + private synchronized void doDispatch(final Runnable runnable) { + if (dispatched) { + throw UndertowServletMessages.MESSAGES.asyncRequestAlreadyDispatched(); + } + dispatched = true; + final HttpServletRequestImpl request = servletRequestContext.getOriginalRequest(); + addAsyncTask(new Runnable() { + @Override + public void run() { + request.asyncRequestDispatched(); + runnable.run(); + } + }); + if (timeoutKey != null) { + timeoutKey.remove(); + } + } + + + private final class TimeoutTask implements Runnable { + + @Override + public void run() { + synchronized (AsyncContextImpl.this) { + if (!dispatched) { + UndertowServletLogger.REQUEST_LOGGER.debug("Async request timed out"); + onAsyncTimeout(); + if (!dispatched) { + if(!getResponse().isCommitted()) { + //servlet + try { + if (servletResponse instanceof HttpServletResponse) { + ((HttpServletResponse) servletResponse).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } else { + servletRequestContext.getOriginalResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } catch (IOException e) { + UndertowLogger.REQUEST_IO_LOGGER.ioException(e); + } + } else { + //not much we can do, just break the connection + IoUtils.safeClose(exchange.getConnection()); + } + if (!dispatched) { + complete(); + } + } + } + } + } + } + + private synchronized void processAsyncTask() { + if (!initialRequestDone) { + return; + } + updateTimeout(); + final Runnable task = asyncTaskQueue.poll(); + if (task != null) { + processingAsyncTask = true; + asyncExecutor().execute(new TaskDispatchRunnable(task)); + } else { + processingAsyncTask = false; + } + } + + /** + * Adds a task to be run to the async context. These tasks are run one at a time, + * after the initial request is finished. If the request is dispatched before the initial + * request is complete then these tasks will not be run + *

+ *

+ * This method is intended to be used to queue read and write tasks for async streams, + * to make sure that multiple threads do not end up working on the same exchange at once + * + * @param runnable The runnable + */ + public synchronized void addAsyncTask(final Runnable runnable) { + asyncTaskQueue.add(runnable); + if (!processingAsyncTask) { + processAsyncTask(); + } + } + + private class TaskDispatchRunnable implements Runnable { + + private final Runnable task; + + private TaskDispatchRunnable(final Runnable task) { + this.task = task; + } + + @Override + public void run() { + try { + task.run(); + } finally { + processAsyncTask(); + } + } + } + + + private void onAsyncComplete() { + final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; + ThreadSetupAction.Handle handle = null; + if (setupRequired) { + handle = servletRequestContext.getDeployment().getThreadSetupAction().setup(exchange); + } + try { + //now run request listeners + setupRequestContext(setupRequired); + try { + for (final BoundAsyncListener listener : asyncListeners) { + AsyncEvent event = new AsyncEvent(this, listener.servletRequest, listener.servletResponse); + try { + listener.asyncListener.onComplete(event); + } catch (IOException e) { + UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); + } + } + } finally { + tearDownRequestContext(setupRequired); + } + } finally { + if (setupRequired) { + handle.tearDown(); + } + } + } + + private void onAsyncTimeout() { + final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; + ThreadSetupAction.Handle handle = null; + if (setupRequired) { + handle = servletRequestContext.getDeployment().getThreadSetupAction().setup(exchange); + } + try { + //now run request listeners + setupRequestContext(setupRequired); + try { + for (final BoundAsyncListener listener : asyncListeners) { + AsyncEvent event = new AsyncEvent(this, listener.servletRequest, listener.servletResponse); + try { + listener.asyncListener.onTimeout(event); + } catch (IOException e) { + UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); + } + } + } finally { + tearDownRequestContext(setupRequired); + } + } finally { + if (setupRequired) { + handle.tearDown(); + } + } + } + + private void onAsyncStart(AsyncContext newAsyncContext) { + final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; + ThreadSetupAction.Handle handle = null; + if (setupRequired) { + handle = servletRequestContext.getDeployment().getThreadSetupAction().setup(exchange); + } + try { + //now run request listeners + setupRequestContext(setupRequired); + try { + for (final BoundAsyncListener listener : asyncListeners) { + //make sure we use the new async context + AsyncEvent event = new AsyncEvent(newAsyncContext, listener.servletRequest, listener.servletResponse); + try { + listener.asyncListener.onStartAsync(event); + } catch (IOException e) { + UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); + } + } + } finally { + tearDownRequestContext(setupRequired); + } + } finally { + if (setupRequired) { + handle.tearDown(); + } + } + } + + private void onAsyncError(Throwable t) { + final boolean setupRequired = SecurityActions.currentServletRequestContext() == null; + ThreadSetupAction.Handle handle = null; + if (setupRequired) { + handle = servletRequestContext.getDeployment().getThreadSetupAction().setup(exchange); + } + try { + //now run request listeners + setupRequestContext(setupRequired); + try { + for (final BoundAsyncListener listener : asyncListeners) { + AsyncEvent event = new AsyncEvent(this, listener.servletRequest, listener.servletResponse, t); + try { + listener.asyncListener.onError(event); + } catch (IOException e) { + UndertowServletLogger.REQUEST_LOGGER.ioExceptionDispatchingAsyncEvent(e); + } + } + } finally { + tearDownRequestContext(setupRequired); + } + } finally { + if (setupRequired) { + handle.tearDown(); + } + } + } + + private void setupRequestContext(final boolean setupRequired) { + if (setupRequired) { + servletRequestContext.getDeployment().getApplicationListeners().requestInitialized(servletRequest); + SecurityActions.setCurrentRequestContext(servletRequestContext); + } + } + + private void tearDownRequestContext(final boolean setupRequired) { + if (setupRequired) { + servletRequestContext.getDeployment().getApplicationListeners().requestDestroyed(servletRequest); + SecurityActions.clearCurrentServletAttachments(); + } + } + + private final class BoundAsyncListener { + final AsyncListener asyncListener; + final ServletRequest servletRequest; + final ServletResponse servletResponse; + + private BoundAsyncListener(final AsyncListener asyncListener, final ServletRequest servletRequest, final ServletResponse servletResponse) { + this.asyncListener = asyncListener; + this.servletRequest = servletRequest; + this.servletResponse = servletResponse; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterConfigImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterConfigImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterConfigImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,61 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.util.Enumeration; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.util.IteratorEnumeration; + +/** + * @author Stuart Douglas + */ +public class FilterConfigImpl implements FilterConfig { + + private final FilterInfo filterInfo; + private final ServletContext servletContext; + + public FilterConfigImpl(final FilterInfo filterInfo, final ServletContext servletContext) { + this.filterInfo = filterInfo; + this.servletContext = servletContext; + } + + @Override + public String getFilterName() { + return filterInfo.getName(); + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(final String name) { + return filterInfo.getInitParams().get(name); + } + + @Override + public Enumeration getInitParameterNames() { + return new IteratorEnumeration(filterInfo.getInitParams().keySet().iterator()); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterRegistrationImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterRegistrationImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/FilterRegistrationImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,173 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; + +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.FilterMappingInfo; + +/** + * @author Stuart Douglas + */ +public class FilterRegistrationImpl implements FilterRegistration, FilterRegistration.Dynamic { + + private final FilterInfo filterInfo; + private final Deployment deployment; + + public FilterRegistrationImpl(final FilterInfo filterInfo, final Deployment deployment) { + this.filterInfo = filterInfo; + this.deployment = deployment; + } + + @Override + public void addMappingForServletNames(final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... servletNames) { + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + + for(final String servlet : servletNames){ + if(isMatchAfter) { + if(dispatcherTypes == null || dispatcherTypes.isEmpty()) { + deploymentInfo.addFilterServletNameMapping(filterInfo.getName(), servlet, DispatcherType.REQUEST); + } else { + for(final DispatcherType dispatcher : dispatcherTypes) { + deploymentInfo.addFilterServletNameMapping(filterInfo.getName(), servlet, dispatcher); + } + } + } else { + if(dispatcherTypes == null || dispatcherTypes.isEmpty()) { + deploymentInfo.insertFilterServletNameMapping(0, filterInfo.getName(), servlet, DispatcherType.REQUEST); + } else { + for(final DispatcherType dispatcher : dispatcherTypes) { + deploymentInfo.insertFilterServletNameMapping(0, filterInfo.getName(), servlet, dispatcher); + } + } + } + } + deployment.getServletPaths().invalidate(); + } + + @Override + public Collection getServletNameMappings() { + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + final List ret = new ArrayList(); + for(final FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { + if(mapping.getMappingType() == FilterMappingInfo.MappingType.SERVLET) { + if(mapping.getFilterName().equals(filterInfo.getName())) { + ret.add(mapping.getMapping()); + } + } + } + return ret; + } + + @Override + public void addMappingForUrlPatterns(final EnumSet dispatcherTypes, final boolean isMatchAfter, final String... urlPatterns) { + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + for(final String url : urlPatterns){ + if(isMatchAfter) { + if(dispatcherTypes == null || dispatcherTypes.isEmpty()) { + deploymentInfo.addFilterUrlMapping(filterInfo.getName(), url, DispatcherType.REQUEST); + } else { + for(final DispatcherType dispatcher : dispatcherTypes) { + deploymentInfo.addFilterUrlMapping(filterInfo.getName(), url, dispatcher); + } + } + } else { + if(dispatcherTypes == null || dispatcherTypes.isEmpty()) { + deploymentInfo.insertFilterUrlMapping(0, filterInfo.getName(), url, DispatcherType.REQUEST); + } else { + for(final DispatcherType dispatcher : dispatcherTypes) { + deploymentInfo.insertFilterUrlMapping(0, filterInfo.getName(), url, dispatcher); + } + } + } + } + deployment.getServletPaths().invalidate(); + } + + @Override + public Collection getUrlPatternMappings() { + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + final List ret = new ArrayList(); + for(final FilterMappingInfo mapping : deploymentInfo.getFilterMappings()) { + if(mapping.getMappingType() == FilterMappingInfo.MappingType.URL) { + if(mapping.getFilterName().equals(filterInfo.getName())) { + ret.add(mapping.getMapping()); + } + } + } + return ret; + } + + @Override + public String getName() { + return filterInfo.getName(); + } + + @Override + public String getClassName() { + return filterInfo.getFilterClass().getName(); + } + + @Override + public boolean setInitParameter(final String name, final String value) { + if(filterInfo.getInitParams().containsKey(name)) { + return false; + } + filterInfo.addInitParam(name, value); + return true; + } + + @Override + public String getInitParameter(final String name) { + return filterInfo.getInitParams().get(name); + } + + @Override + public Set setInitParameters(final Map initParameters) { + final Set ret = new HashSet(); + for(Map.Entry entry : initParameters.entrySet()) { + if(!setInitParameter(entry.getKey(), entry.getValue())) { + ret.add(entry.getKey()); + } + } + return ret; + } + + @Override + public Map getInitParameters() { + return filterInfo.getInitParams(); + } + + @Override + public void setAsyncSupported(final boolean isAsyncSupported) { + filterInfo.setAsyncSupported(isAsyncSupported); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletRequestImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletRequestImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletRequestImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,1053 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import io.undertow.security.api.SecurityContext; +import io.undertow.security.idm.Account; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormData; +import io.undertow.server.handlers.form.FormDataParser; +import io.undertow.server.handlers.form.MultiPartParserDefinition; +import io.undertow.server.session.Session; +import io.undertow.server.session.SessionConfig; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.AuthorizationManager; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import io.undertow.servlet.core.ManagedServlet; +import io.undertow.servlet.core.ServletUpgradeListener; +import io.undertow.servlet.handlers.ServletChain; +import io.undertow.servlet.handlers.ServletPathMatch; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.util.EmptyEnumeration; +import io.undertow.servlet.util.IteratorEnumeration; +import io.undertow.util.CanonicalPathUtils; +import io.undertow.util.DateUtils; +import io.undertow.util.HeaderMap; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.LocaleUtils; +import io.undertow.util.Methods; +import org.xnio.LocalSocketAddress; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.security.AccessController; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Deque; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * The http servlet request implementation. This class is not thread safe + * + * @author Stuart Douglas + */ +public final class HttpServletRequestImpl implements HttpServletRequest { + + private static final String HTTPS = "https"; + + private final HttpServerExchange exchange; + private final ServletContextImpl originalServletContext; + private ServletContextImpl servletContext; + + private Map attributes = null; + + private ServletInputStream servletInputStream; + private BufferedReader reader; + + private Cookie[] cookies; + private List parts = null; + private volatile boolean asyncStarted = false; + private volatile AsyncContextImpl asyncContext = null; + private Map> queryParameters; + private FormData parsedFormData; + private Charset characterEncoding; + private boolean readStarted; + private SessionConfig.SessionCookieSource sessionCookieSource; + + public HttpServletRequestImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) { + this.exchange = exchange; + this.servletContext = servletContext; + this.originalServletContext = servletContext; + } + + public HttpServerExchange getExchange() { + return exchange; + } + + @Override + public String getAuthType() { + SecurityContext securityContext = exchange.getSecurityContext(); + + return securityContext != null ? securityContext.getMechanismName() : null; + } + + @Override + public Cookie[] getCookies() { + if (cookies == null) { + Map cookies = exchange.getRequestCookies(); + if (cookies.isEmpty()) { + return null; + } + Cookie[] value = new Cookie[cookies.size()]; + int i = 0; + for (Map.Entry entry : cookies.entrySet()) { + io.undertow.server.handlers.Cookie cookie = entry.getValue(); + Cookie c = new Cookie(cookie.getName(), cookie.getValue()); + if (cookie.getDomain() != null) { + c.setDomain(cookie.getDomain()); + } + c.setHttpOnly(cookie.isHttpOnly()); + if (cookie.getMaxAge() != null) { + c.setMaxAge(cookie.getMaxAge()); + } + if (cookie.getPath() != null) { + c.setPath(cookie.getPath()); + } + c.setSecure(cookie.isSecure()); + c.setVersion(cookie.getVersion()); + value[i++] = c; + } + this.cookies = value; + } + return cookies; + } + + @Override + public long getDateHeader(final String name) { + String header = exchange.getRequestHeaders().getFirst(name); + if (header == null) { + return -1; + } + Date date = DateUtils.parseDate(header); + if (date == null) { + throw UndertowServletMessages.MESSAGES.headerCannotBeConvertedToDate(header); + } + return date.getTime(); + } + + @Override + public String getHeader(final String name) { + HeaderMap headers = exchange.getRequestHeaders(); + return headers.getFirst(name); + } + + public String getHeader(final HttpString name) { + HeaderMap headers = exchange.getRequestHeaders(); + return headers.getFirst(name); + } + + + @Override + public Enumeration getHeaders(final String name) { + List headers = exchange.getRequestHeaders().get(name); + if (headers == null) { + return EmptyEnumeration.instance(); + } + return new IteratorEnumeration(headers.iterator()); + } + + @Override + public Enumeration getHeaderNames() { + final Set headers = new HashSet(); + for (final HttpString i : exchange.getRequestHeaders().getHeaderNames()) { + headers.add(i.toString()); + } + return new IteratorEnumeration(headers.iterator()); + } + + @Override + public int getIntHeader(final String name) { + String header = getHeader(name); + if (header == null) { + return -1; + } + return Integer.parseInt(header); + } + + @Override + public String getMethod() { + return exchange.getRequestMethod().toString(); + } + + @Override + public String getPathInfo() { + ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch(); + if (match != null) { + return match.getRemaining(); + } + return null; + } + + @Override + public String getPathTranslated() { + return getRealPath(getPathInfo()); + } + + @Override + public String getContextPath() { + return servletContext.getContextPath(); + } + + @Override + public String getQueryString() { + return exchange.getQueryString().isEmpty() ? null : exchange.getQueryString(); + } + + @Override + public String getRemoteUser() { + Principal userPrincipal = getUserPrincipal(); + + return userPrincipal != null ? userPrincipal.getName() : null; + } + + @Override + public boolean isUserInRole(final String role) { + if (role == null) { + return false; + } + //according to the servlet spec this aways returns false + if (role.equals("*")) { + return false; + } + SecurityContext sc = exchange.getSecurityContext(); + Account account = sc.getAuthenticatedAccount(); + if (account == null) { + return false; + } + ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + + if (role.equals("**")) { + Set roles = servletRequestContext.getDeployment().getDeploymentInfo().getSecurityRoles(); + if (!roles.contains("**")) { + return true; + } + } + + final ServletChain servlet = servletRequestContext.getCurrentServlet(); + final Deployment deployment = servletContext.getDeployment(); + final AuthorizationManager authorizationManager = deployment.getDeploymentInfo().getAuthorizationManager(); + return authorizationManager.isUserInRole(role, account, servlet.getManagedServlet().getServletInfo(), this, deployment); + } + + @Override + public Principal getUserPrincipal() { + SecurityContext securityContext = exchange.getSecurityContext(); + Principal result = null; + Account account = null; + if (securityContext != null && (account = securityContext.getAuthenticatedAccount()) != null) { + result = account.getPrincipal(); + } + return result; + } + + @Override + public String getRequestedSessionId() { + SessionConfig config = originalServletContext.getSessionConfig(); + return config.findSessionId(exchange); + } + + @Override + public String changeSessionId() { + HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false); + if (session == null) { + throw UndertowServletMessages.MESSAGES.noSession(); + } + String oldId = session.getId(); + Session underlyingSession; + if(System.getSecurityManager() == null) { + underlyingSession = session.getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + String newId = underlyingSession.changeSessionId(exchange, originalServletContext.getSessionConfig()); + servletContext.getDeployment().getApplicationListeners().httpSessionIdChanged(session, oldId); + return newId; + } + + @Override + public String getRequestURI() { + //we need the non-decoded string, which means we need to use exchange.getRequestURI() + if(exchange.isHostIncludedInRequestURI()) { + //we need to strip out the host part + String uri = exchange.getRequestURI(); + int slashes =0; + for(int i = 0; i < uri.length(); ++i) { + if(uri.charAt(i) == '/') { + if(++slashes == 3) { + return uri.substring(i); + } + } + } + return "/"; + } else { + return exchange.getRequestURI(); + } + } + + @Override + public StringBuffer getRequestURL() { + return new StringBuffer(exchange.getRequestURL()); + } + + @Override + public String getServletPath() { + ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch(); + if (match != null) { + return match.getMatched(); + } + return ""; + } + + @Override + public HttpSession getSession(final boolean create) { + return servletContext.getSession(originalServletContext, exchange, create); + } + + @Override + public HttpSession getSession() { + return getSession(true); + } + + + @Override + public boolean isRequestedSessionIdValid() { + HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false); + return session != null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return sessionCookieSource() == SessionConfig.SessionCookieSource.COOKIE; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return sessionCookieSource() == SessionConfig.SessionCookieSource.URL; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return isRequestedSessionIdFromURL(); + } + + @Override + public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException { + if (response.isCommitted()) { + throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); + } + + SecurityContext sc = exchange.getSecurityContext(); + sc.setAuthenticationRequired(); + // TODO: this will set the status code and headers without going through any potential + // wrappers, is this a problem? + if (sc.authenticate()) { + if (sc.isAuthenticated()) { + return true; + } else { + throw UndertowServletMessages.MESSAGES.authenticationFailed(); + } + } else { + // Not authenticated and response already sent. + HttpServletResponseImpl responseImpl = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalResponse(); + responseImpl.closeStreamAndWriter(); + return false; + } + } + + @Override + public void login(final String username, final String password) throws ServletException { + if (username == null || password == null) { + throw UndertowServletMessages.MESSAGES.loginFailed(); + } + SecurityContext sc = exchange.getSecurityContext(); + if (sc.isAuthenticated()) { + throw UndertowServletMessages.MESSAGES.userAlreadyLoggedIn(); + } + boolean login = false; + try { + login = sc.login(username, password); + } + catch (SecurityException se) { + if (se.getCause() instanceof ServletException) + throw (ServletException) se.getCause(); + throw new ServletException(se); + } + if (!login) { + throw UndertowServletMessages.MESSAGES.loginFailed(); + } + } + + @Override + public void logout() throws ServletException { + SecurityContext sc = exchange.getSecurityContext(); + sc.logout(); + if(servletContext.getDeployment().getDeploymentInfo().isInvalidateSessionOnLogout()) { + HttpSession session = getSession(false); + if(session != null) { + session.invalidate(); + } + } + } + + @Override + public Collection getParts() throws IOException, ServletException { + if (parts == null) { + loadParts(); + } + return parts; + } + + @Override + public Part getPart(final String name) throws IOException, ServletException { + if (parts == null) { + loadParts(); + } + for (Part part : parts) { + if (part.getName().equals(name)) { + return part; + } + } + return null; + } + + @Override + public T upgrade(final Class handlerClass) throws IOException { + try { + InstanceFactory factory = servletContext.getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(handlerClass); + final InstanceHandle instance = factory.createInstance(); + exchange.upgradeChannel(new ServletUpgradeListener(instance, servletContext.getDeployment().getThreadSetupAction(), exchange)); + return instance.getInstance(); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private void loadParts() throws IOException, ServletException { + final ServletRequestContext requestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + + if (parts == null) { + final List parts = new ArrayList(); + String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (mimeType != null && mimeType.startsWith(MultiPartParserDefinition.MULTIPART_FORM_DATA)) { + + FormData formData = parseFormData(); + if(formData != null) { + for (final String namedPart : formData) { + for (FormData.FormValue part : formData.get(namedPart)) { + parts.add(new PartImpl(namedPart, part, requestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getServletInfo().getMultipartConfig(), servletContext)); + } + } + } + } else { + throw UndertowServletMessages.MESSAGES.notAMultiPartRequest(); + } + this.parts = parts; + } + } + + @Override + public Object getAttribute(final String name) { + if (attributes == null) { + return null; + } + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + if (attributes == null) { + return EmptyEnumeration.instance(); + } + return new IteratorEnumeration(attributes.keySet().iterator()); + } + + @Override + public String getCharacterEncoding() { + if (characterEncoding != null) { + return characterEncoding.name(); + } + String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (contentType == null) { + return null; + } + return Headers.extractQuotedValueFromHeader(contentType, "charset"); + } + + @Override + public void setCharacterEncoding(final String env) throws UnsupportedEncodingException { + if (readStarted) { + return; + } + try { + characterEncoding = Charset.forName(env); + + final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalServletPathMatch().getServletChain().getManagedServlet(); + final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); + if (parser != null) { + parser.setCharacterEncoding(env); + } + } catch (UnsupportedCharsetException e) { + throw new UnsupportedEncodingException(); + } + } + + @Override + public int getContentLength() { + long length = getContentLengthLong(); + if(length > Integer.MAX_VALUE) { + return -1; + } + return (int)length; + } + + @Override + public long getContentLengthLong() { + final String contentLength = getHeader(Headers.CONTENT_LENGTH); + if (contentLength == null || contentLength.isEmpty()) { + return -1; + } + return Long.parseLong(contentLength); + } + + @Override + public String getContentType() { + return getHeader(Headers.CONTENT_TYPE); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + if (reader != null) { + throw UndertowServletMessages.MESSAGES.getReaderAlreadyCalled(); + } + if(servletInputStream == null) { + servletInputStream = new ServletInputStreamImpl(this); + } + readStarted = true; + return servletInputStream; + } + + public void closeAndDrainRequest() throws IOException { + if(reader != null) { + reader.close(); + } + if(servletInputStream == null) { + servletInputStream = new ServletInputStreamImpl(this); + } + servletInputStream.close(); + } + + @Override + public String getParameter(final String name) { + if(queryParameters == null) { + queryParameters = exchange.getQueryParameters(); + } + Deque params = queryParameters.get(name); + if (params == null) { + if (exchange.getRequestMethod().equals(Methods.POST)) { + final FormData parsedFormData = parseFormData(); + if (parsedFormData != null) { + FormData.FormValue res = parsedFormData.getFirst(name); + if (res == null || res.isFile()) { + return null; + } else { + return res.getValue(); + } + } + } + return null; + } + return params.getFirst(); + } + + @Override + public Enumeration getParameterNames() { + if (queryParameters == null) { + queryParameters = exchange.getQueryParameters(); + } + final Set parameterNames = new HashSet(queryParameters.keySet()); + if (exchange.getRequestMethod().equals(Methods.POST)) { + final FormData parsedFormData = parseFormData(); + if (parsedFormData != null) { + Iterator it = parsedFormData.iterator(); + while (it.hasNext()) { + String name = it.next(); + for(FormData.FormValue param : parsedFormData.get(name)) { + if(!param.isFile()) { + parameterNames.add(name); + break; + } + } + } + } + } + return new IteratorEnumeration(parameterNames.iterator()); + } + + @Override + public String[] getParameterValues(final String name) { + if (queryParameters == null) { + queryParameters = exchange.getQueryParameters(); + } + final List ret = new ArrayList(); + Deque params = queryParameters.get(name); + if (params != null) { + for (String param : params) { + ret.add(param); + } + } + if (exchange.getRequestMethod().equals(Methods.POST)) { + final FormData parsedFormData = parseFormData(); + if (parsedFormData != null) { + Deque res = parsedFormData.get(name); + if (res != null) { + for (FormData.FormValue value : res) { + if(!value.isFile()) { + ret.add(value.getValue()); + } + } + } + } + } + if (ret.isEmpty()) { + return null; + } + return ret.toArray(new String[ret.size()]); + } + + @Override + public Map getParameterMap() { + if (queryParameters == null) { + queryParameters = exchange.getQueryParameters(); + } + final Map> arrayMap = new HashMap>(); + for (Map.Entry> entry : queryParameters.entrySet()) { + arrayMap.put(entry.getKey(), new ArrayList(entry.getValue())); + } + if (exchange.getRequestMethod().equals(Methods.POST)) { + + final FormData parsedFormData = parseFormData(); + if (parsedFormData != null) { + Iterator it = parsedFormData.iterator(); + while (it.hasNext()) { + final String name = it.next(); + Deque val = parsedFormData.get(name); + if (arrayMap.containsKey(name)) { + ArrayList existing = arrayMap.get(name); + for (final FormData.FormValue v : val) { + if(!v.isFile()) { + existing.add(v.getValue()); + } + } + } else { + final ArrayList values = new ArrayList(); + int i = 0; + for (final FormData.FormValue v : val) { + if(!v.isFile()) { + values.add(v.getValue()); + } + } + arrayMap.put(name, values); + } + } + } + } + final Map ret = new HashMap(); + for(Map.Entry> entry : arrayMap.entrySet()) { + ret.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()])); + } + return ret; + } + + private FormData parseFormData() { + if (parsedFormData == null) { + if (readStarted) { + return null; + } + readStarted = true; + final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalServletPathMatch().getServletChain().getManagedServlet(); + final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange); + if (parser == null) { + return null; + } + try { + return parsedFormData = parser.parseBlocking(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return parsedFormData; + } + + @Override + public String getProtocol() { + return exchange.getProtocol().toString(); + } + + @Override + public String getScheme() { + return exchange.getRequestScheme(); + } + + @Override + public String getServerName() { + return exchange.getHostName(); + } + + @Override + public int getServerPort() { + return exchange.getHostPort(); + } + + @Override + public BufferedReader getReader() throws IOException { + if (reader == null) { + if (servletInputStream != null) { + throw UndertowServletMessages.MESSAGES.getInputStreamAlreadyCalled(); + } + Charset charSet = servletContext.getDeployment().getDefaultCharset(); + if (characterEncoding != null) { + charSet = characterEncoding; + } else { + String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE); + if (contentType != null) { + String c = Headers.extractQuotedValueFromHeader(contentType, "charset"); + if (c != null) { + try { + charSet = Charset.forName(c); + } catch (UnsupportedCharsetException e) { + throw new UnsupportedEncodingException(); + } + } + } + } + + reader = new BufferedReader(new InputStreamReader(exchange.getInputStream(), charSet)); + } + readStarted = true; + return reader; + } + + @Override + public String getRemoteAddr() { + InetSocketAddress sourceAddress = exchange.getSourceAddress(); + if(sourceAddress == null) { + return ""; + } + InetAddress address = sourceAddress.getAddress(); + if(address == null) { + //this is unresolved, so we just return the host name + //not exactly spec, but if the name should be resolved then a PeerNameResolvingHandler should be used + //and this is probably better than just returning null + return sourceAddress.getHostString(); + } + return address.getHostAddress(); + } + + @Override + public String getRemoteHost() { + InetSocketAddress sourceAddress = exchange.getSourceAddress(); + if(sourceAddress == null) { + return ""; + } + return sourceAddress.getHostString(); + } + + @Override + public void setAttribute(final String name, final Object object) { + if (attributes == null) { + attributes = new HashMap(); + } + Object existing = attributes.put(name, object); + if (existing != null) { + servletContext.getDeployment().getApplicationListeners().servletRequestAttributeReplaced(this, name, existing); + } else { + servletContext.getDeployment().getApplicationListeners().servletRequestAttributeAdded(this, name, object); + } + } + + @Override + public void removeAttribute(final String name) { + if (attributes == null) { + return; + } + Object exiting = attributes.remove(name); + servletContext.getDeployment().getApplicationListeners().servletRequestAttributeRemoved(this, name, exiting); + } + + @Override + public Locale getLocale() { + return getLocales().nextElement(); + } + + @Override + public Enumeration getLocales() { + final List acceptLanguage = exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE); + List ret = LocaleUtils.getLocalesFromHeader(acceptLanguage); + return new IteratorEnumeration(ret.iterator()); + } + + @Override + public boolean isSecure() { + return getScheme().equalsIgnoreCase(HTTPS); + } + + @Override + public RequestDispatcher getRequestDispatcher(final String path) { + String realPath; + if (path.startsWith("/")) { + realPath = path; + } else { + String current = exchange.getRelativePath(); + int lastSlash = current.lastIndexOf("/"); + if (lastSlash != -1) { + current = current.substring(0, lastSlash + 1); + } + realPath = CanonicalPathUtils.canonicalize(current + path); + } + return new RequestDispatcherImpl(realPath, servletContext); + } + + @Override + public String getRealPath(final String path) { + return servletContext.getRealPath(path); + } + + @Override + public int getRemotePort() { + return exchange.getSourceAddress().getPort(); + } + + @Override + public String getLocalName() { + return exchange.getDestinationAddress().getHostName(); + } + + @Override + public String getLocalAddr() { + SocketAddress address = exchange.getConnection().getLocalAddress(); + if (address instanceof InetSocketAddress) { + return ((InetSocketAddress) address).getAddress().getHostAddress(); + } else if (address instanceof LocalSocketAddress) { + return ((LocalSocketAddress) address).getName(); + } + return null; + } + + @Override + public int getLocalPort() { + SocketAddress address = exchange.getConnection().getLocalAddress(); + if (address instanceof InetSocketAddress) { + return ((InetSocketAddress) address).getPort(); + } + return -1; + } + + @Override + public ServletContextImpl getServletContext() { + return servletContext; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + if (!isAsyncSupported()) { + throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed(); + } else if (asyncStarted) { + throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted(); + } + asyncStarted = true; + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + return asyncContext = new AsyncContextImpl(exchange, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), servletRequestContext, false, asyncContext); + } + + @Override + public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse) throws IllegalStateException { + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { + if (servletRequestContext.getOriginalRequest() != servletRequest) { + if (!(servletRequest instanceof ServletRequestWrapper)) { + throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(servletRequest); + } + } + if (servletRequestContext.getOriginalResponse() != servletResponse) { + if (!(servletResponse instanceof ServletResponseWrapper)) { + throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(servletResponse); + } + } + } + if (!isAsyncSupported()) { + throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed(); + } else if (asyncStarted) { + throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted(); + } + asyncStarted = true; + return asyncContext = new AsyncContextImpl(exchange, servletRequest, servletResponse, servletRequestContext, true, asyncContext); + } + + @Override + public boolean isAsyncStarted() { + return asyncStarted; + } + + @Override + public boolean isAsyncSupported() { + Boolean supported = exchange.getAttachment(AsyncContextImpl.ASYNC_SUPPORTED); + return supported == null || supported; + } + + @Override + public AsyncContextImpl getAsyncContext() { + if (!asyncStarted) { + throw UndertowServletMessages.MESSAGES.asyncNotStarted(); + } + return asyncContext; + } + + public AsyncContextImpl getAsyncContextInternal() { + return asyncContext; + } + + @Override + public DispatcherType getDispatcherType() { + return exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType(); + } + + + public Map> getQueryParameters() { + if (queryParameters == null) { + queryParameters = exchange.getQueryParameters(); + } + return queryParameters; + } + + public void setQueryParameters(final Map> queryParameters) { + this.queryParameters = queryParameters; + } + + public void setServletContext(final ServletContextImpl servletContext) { + this.servletContext = servletContext; + } + + void asyncRequestDispatched() { + asyncStarted = false; + } + + public String getOriginalRequestURI() { + String uri = (String) getAttribute(RequestDispatcher.FORWARD_REQUEST_URI); + if(uri != null) { + return uri; + } + uri = (String) getAttribute(AsyncContext.ASYNC_REQUEST_URI); + if(uri != null) { + return uri; + } + return getRequestURI(); + } + + + public String getOriginalServletPath() { + String uri = (String) getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH); + if(uri != null) { + return uri; + } + uri = (String) getAttribute(AsyncContext.ASYNC_SERVLET_PATH); + if(uri != null) { + return uri; + } + return getServletPath(); + } + + public String getOriginalPathInfo() { + String uri = (String) getAttribute(RequestDispatcher.FORWARD_PATH_INFO); + if(uri != null) { + return uri; + } + uri = (String) getAttribute(AsyncContext.ASYNC_PATH_INFO); + if(uri != null) { + return uri; + } + return getPathInfo(); + } + + public String getOriginalContextPath() { + String uri = (String) getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH); + if(uri != null) { + return uri; + } + uri = (String) getAttribute(AsyncContext.ASYNC_CONTEXT_PATH); + if(uri != null) { + return uri; + } + return getContextPath(); + } + + public String getOriginalQueryString() { + String uri = (String) getAttribute(RequestDispatcher.FORWARD_QUERY_STRING); + if(uri != null) { + return uri; + } + uri = (String) getAttribute(AsyncContext.ASYNC_QUERY_STRING); + if(uri != null) { + return uri; + } + return getQueryString(); + } + + private SessionConfig.SessionCookieSource sessionCookieSource() { + if(sessionCookieSource == null) { + sessionCookieSource = originalServletContext.getSessionConfig().sessionCookieSource(exchange); + } + return sessionCookieSource; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletResponseImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletResponseImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpServletResponseImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,716 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.SessionTrackingMode; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import io.undertow.server.HttpServerExchange; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.util.CanonicalPathUtils; +import io.undertow.util.DateUtils; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.RedirectBuilder; +import io.undertow.util.StatusCodes; + + +/** + * @author Stuart Douglas + */ +public final class HttpServletResponseImpl implements HttpServletResponse { + + private final HttpServerExchange exchange; + private final ServletContextImpl originalServletContext; + private volatile ServletContextImpl servletContext; + + private ServletOutputStreamImpl servletOutputStream; + private ResponseState responseState = ResponseState.NONE; + private PrintWriter writer; + private Integer bufferSize; + private long contentLength = -1; + private boolean insideInclude = false; + private Locale locale; + private boolean responseDone = false; + + private boolean ignoredFlushPerformed = false; + + + private boolean charsetSet = false; //if a content type has been set either implicitly or implicitly + private String contentType; + private String charset; + + public HttpServletResponseImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) { + this.exchange = exchange; + this.servletContext = servletContext; + this.originalServletContext = servletContext; + } + + public HttpServerExchange getExchange() { + return exchange; + } + + @Override + public void addCookie(final Cookie cookie) { + if (insideInclude) { + return; + } + final ServletCookieAdaptor servletCookieAdaptor = new ServletCookieAdaptor(cookie); + if (cookie.getVersion() == 0) { + servletCookieAdaptor.setVersion(servletContext.getDeployment().getDeploymentInfo().getDefaultCookieVersion()); + } + exchange.setResponseCookie(servletCookieAdaptor); + } + + @Override + public boolean containsHeader(final String name) { + return exchange.getResponseHeaders().contains(name); + } + + @Override + public String encodeUrl(final String url) { + return encodeURL(url); + } + + @Override + public String encodeRedirectUrl(final String url) { + return encodeRedirectURL(url); + } + + @Override + public void sendError(final int sc, final String msg) throws IOException { + if (responseStarted()) { + throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); + } + resetBuffer(); + writer = null; + responseState = ResponseState.NONE; + exchange.setResponseCode(sc); + //todo: is this the best way to handle errors? + final String location = servletContext.getDeployment().getErrorPages().getErrorLocation(sc); + if (location != null) { + RequestDispatcherImpl requestDispatcher = new RequestDispatcherImpl(location, servletContext); + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + try { + requestDispatcher.error(servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet().getServletInfo().getName(), msg); + } catch (ServletException e) { + throw new RuntimeException(e); + } + } else if (msg != null) { + setContentType("text/html"); + getWriter().write("Error" + msg + ""); + getWriter().close(); + } + responseDone(); + } + + @Override + public void sendError(final int sc) throws IOException { + sendError(sc, StatusCodes.getReason(sc)); + } + + @Override + public void sendRedirect(final String location) throws IOException { + if (responseStarted()) { + throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); + } + resetBuffer(); + setStatus(302); + String realPath; + if (location.contains("://")) {//absolute url + exchange.getResponseHeaders().put(Headers.LOCATION, location); + } else { + if (location.startsWith("/")) { + realPath = location; + } else { + String current = exchange.getRelativePath(); + int lastSlash = current.lastIndexOf("/"); + if (lastSlash != -1) { + current = current.substring(0, lastSlash + 1); + } + realPath = CanonicalPathUtils.canonicalize(servletContext.getContextPath() + current + location); + } + String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + realPath; + exchange.getResponseHeaders().put(Headers.LOCATION, loc); + } + responseDone(); + } + + @Override + public void setDateHeader(final String name, final long date) { + setHeader(name, DateUtils.toDateString(new Date(date))); + } + + @Override + public void addDateHeader(final String name, final long date) { + addHeader(name, DateUtils.toDateString(new Date(date))); + } + + @Override + public void setHeader(final String name, final String value) { + setHeader(new HttpString(name), value); + } + + + public void setHeader(final HttpString name, final String value) { + if (insideInclude || ignoredFlushPerformed) { + return; + } + exchange.getResponseHeaders().put(name, value); + } + + @Override + public void addHeader(final String name, final String value) { + addHeader(new HttpString(name), value); + } + + public void addHeader(final HttpString name, final String value) { + if (insideInclude || ignoredFlushPerformed) { + return; + } + exchange.getResponseHeaders().add(name, value); + } + + @Override + public void setIntHeader(final String name, final int value) { + setHeader(name, Integer.toString(value)); + } + + @Override + public void addIntHeader(final String name, final int value) { + addHeader(name, Integer.toString(value)); + } + + @Override + public void setStatus(final int sc) { + if (insideInclude) { + return; + } + if (responseStarted()) { + return; + } + exchange.setResponseCode(sc); + } + + @Override + public void setStatus(final int sc, final String sm) { + if (insideInclude) { + return; + } + setStatus(sc); + } + + @Override + public int getStatus() { + return exchange.getResponseCode(); + } + + @Override + public String getHeader(final String name) { + return exchange.getResponseHeaders().getFirst(name); + } + + @Override + public Collection getHeaders(final String name) { + return new ArrayList(exchange.getResponseHeaders().get(name)); + } + + @Override + public Collection getHeaderNames() { + final Set headers = new HashSet(); + for (final HttpString i : exchange.getResponseHeaders().getHeaderNames()) { + headers.add(i.toString()); + } + return headers; + } + + @Override + public String getCharacterEncoding() { + if (charset == null) { + return servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding(); + } + return charset; + } + + @Override + public String getContentType() { + if (contentType != null) { + if (charsetSet) { + return contentType + ";charset=" + getCharacterEncoding(); + } else { + return contentType; + } + } + return null; + } + + @Override + public ServletOutputStream getOutputStream() { + if (responseState == ResponseState.WRITER) { + throw UndertowServletMessages.MESSAGES.getWriterAlreadyCalled(); + } + responseState = ResponseState.STREAM; + createOutputStream(); + return servletOutputStream; + } + + @Override + public PrintWriter getWriter() throws IOException { + if (writer == null) { + if (!charsetSet) { + //servet 5.5 + setCharacterEncoding(getCharacterEncoding()); + } + if (responseState == ResponseState.STREAM) { + throw UndertowServletMessages.MESSAGES.getOutputStreamAlreadyCalled(); + } + responseState = ResponseState.WRITER; + createOutputStream(); + final ServletPrintWriter servletPrintWriter = new ServletPrintWriter(servletOutputStream, getCharacterEncoding()); + writer = ServletPrintWriterDelegate.newInstance(servletPrintWriter); + } + return writer; + } + + private void createOutputStream() { + if (servletOutputStream == null) { + if (bufferSize == null) { + servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY)); + } else { + servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY), bufferSize); + } + } + } + + @Override + public void setCharacterEncoding(final String charset) { + if (insideInclude || responseStarted() || writer != null || isCommitted()) { + return; + } + charsetSet = true; + this.charset = charset; + if (contentType != null) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType()); + } + } + + @Override + public void setContentLength(final int len) { + if (insideInclude || responseStarted()) { + return; + } + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Integer.toString(len)); + this.contentLength = (long) len; + } + + @Override + public void setContentLengthLong(final long len) { + if (insideInclude || responseStarted()) { + return; + } + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(len)); + this.contentLength = len; + } + + boolean isIgnoredFlushPerformed() { + return ignoredFlushPerformed; + } + + void setIgnoredFlushPerformed(boolean ignoredFlushPerformed) { + this.ignoredFlushPerformed = ignoredFlushPerformed; + } + + private boolean responseStarted() { + return exchange.isResponseStarted() || ignoredFlushPerformed; + } + + @Override + public void setContentType(final String type) { + if (type == null || insideInclude || responseStarted()) { + return; + } + contentType = type; + int split = type.indexOf(";"); + if (split != -1) { + int pos = type.indexOf("charset="); + if (pos != -1) { + int i = pos + "charset=".length(); + do { + char c = type.charAt(i); + if (c == ' ' || c == '\t' || c == ';') { + break; + } + ++i; + } while (i < type.length()); + if (writer == null && !isCommitted()) { + charsetSet = true; + //we only change the charset if the writer has not been retrieved yet + this.charset = type.substring(pos + "charset=".length(), i); + //it is valid for the charset to be enclosed in quotes + if (this.charset.startsWith("\"") && this.charset.endsWith("\"") && this.charset.length() > 1) { + this.charset = this.charset.substring(1, this.charset.length() - 1); + } + } + int charsetStart = pos; + while (type.charAt(--charsetStart) != ';' && charsetStart > 0) { + } + StringBuilder contentTypeBuilder = new StringBuilder(); + contentTypeBuilder.append(type.substring(0, charsetStart)); + if (i != type.length()) { + contentTypeBuilder.append(type.substring(i)); + } + contentType = contentTypeBuilder.toString(); + } + //strip any trailing semicolon + for (int i = contentType.length() - 1; i >= 0; --i) { + char c = contentType.charAt(i); + if (c == ' ' || c == '\t') { + continue; + } + if (c == ';') { + contentType = contentType.substring(0, i); + } + break; + } + } + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType()); + } + + @Override + public void setBufferSize(final int size) { + if (servletOutputStream != null) { + servletOutputStream.setBufferSize(size); + } + this.bufferSize = size; + } + + @Override + public int getBufferSize() { + if (bufferSize == null) { + return exchange.getConnection().getBufferSize(); + } + return bufferSize; + } + + @Override + public void flushBuffer() throws IOException { + if (writer != null) { + writer.flush(); + } else if (servletOutputStream != null) { + servletOutputStream.flush(); + } else { + createOutputStream(); + servletOutputStream.flush(); + } + } + + public void closeStreamAndWriter() throws IOException { + if (writer != null) { + if (!servletOutputStream.isClosed()) { + writer.flush(); + } + writer.close(); + } else { + if (servletOutputStream == null) { + createOutputStream(); + } + //close also flushes + servletOutputStream.close(); + } + } + + @Override + public void resetBuffer() { + if (servletOutputStream != null) { + servletOutputStream.resetBuffer(); + } + if (writer != null) { + writer = new PrintWriter(servletOutputStream, false); + } + } + + @Override + public boolean isCommitted() { + return responseStarted(); + } + + @Override + public void reset() { + if (servletOutputStream != null) { + servletOutputStream.resetBuffer(); + } + writer = null; + responseState = ResponseState.NONE; + exchange.getResponseHeaders().clear(); + exchange.setResponseCode(200); + } + + @Override + public void setLocale(final Locale loc) { + if (insideInclude || responseStarted()) { + return; + } + this.locale = loc; + exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, loc.getLanguage() + "-" + loc.getCountry()); + if (!charsetSet && writer == null) { + final Map localeCharsetMapping = servletContext.getDeployment().getDeploymentInfo().getLocaleCharsetMapping(); + // Match full language_country_variant first, then language_country, + // then language only + String charset = localeCharsetMapping.get(locale.toString()); + if (charset == null) { + charset = localeCharsetMapping.get(locale.getLanguage() + "_" + + locale.getCountry()); + if (charset == null) { + charset = localeCharsetMapping.get(locale.getLanguage()); + } + } + if (charset != null) { + this.charset = charset; + if (contentType != null) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType()); + } + } + } + + } + + @Override + public Locale getLocale() { + if (locale != null) { + return locale; + } + return Locale.getDefault(); + } + + public void responseDone() { + if (responseDone) { + return; + } + servletContext.updateSessionAccessTime(exchange); + responseDone = true; + try { + closeStreamAndWriter(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public boolean isInsideInclude() { + return insideInclude; + } + + public void setInsideInclude(final boolean insideInclude) { + this.insideInclude = insideInclude; + } + + public void setServletContext(final ServletContextImpl servletContext) { + this.servletContext = servletContext; + } + + public ServletContextImpl getServletContext() { + return servletContext; + } + + public String encodeURL(String url) { + String absolute = toAbsolute(url); + if (isEncodeable(absolute)) { + // W3c spec clearly said + if (url.equalsIgnoreCase("")) { + url = absolute; + } + return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId()); + } else { + return (url); + } + + } + + /** + * Encode the session identifier associated with this response + * into the specified redirect URL, if necessary. + * + * @param url URL to be encoded + */ + public String encodeRedirectURL(String url) { + if (isEncodeable(toAbsolute(url))) { + return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId()); + } else { + return (url); + } + } + + /** + * Convert (if necessary) and return the absolute URL that represents the + * resource referenced by this possibly relative URL. If this URL is + * already absolute, return it unchanged. + * + * @param location URL to be (possibly) converted and then returned + * @throws IllegalArgumentException if a MalformedURLException is + * thrown when converting the relative URL to an absolute one + */ + private String toAbsolute(String location) { + + if (location == null) { + return location; + } + + boolean leadingSlash = location.startsWith("/"); + + if (leadingSlash || !hasScheme(location)) { + return RedirectBuilder.redirect(exchange, location, false); + } else { + return location; + } + + } + + /** + * Determine if a URI string has a scheme component. + */ + private boolean hasScheme(String uri) { + int len = uri.length(); + for (int i = 0; i < len; i++) { + char c = uri.charAt(i); + if (c == ':') { + return i > 0; + } else if (!Character.isLetterOrDigit(c) && + (c != '+' && c != '-' && c != '.')) { + return false; + } + } + return false; + } + + /** + * Return true if the specified URL should be encoded with + * a session identifier. This will be true if all of the following + * conditions are met: + *

    + *
  • The request we are responding to asked for a valid session + *
  • The requested session ID was not received via a cookie + *
  • The specified URL points back to somewhere within the web + * application that is responding to this request + *
+ * + * @param location Absolute URL to be validated + */ + protected boolean isEncodeable(final String location) { + + if (location == null) + return (false); + + // Is this an intra-document reference? + if (location.startsWith("#")) + return (false); + + // Are we in a valid session that is not using cookies? + final HttpServletRequestImpl hreq = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalRequest(); + + // Is URL encoding permitted + if (!originalServletContext.getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) { + return false; + } + + final HttpSession session = hreq.getSession(false); + if (session == null) + return (false); + if (hreq.isRequestedSessionIdFromCookie()) + return (false); + + return doIsEncodeable(hreq, session, location); + } + + private boolean doIsEncodeable(HttpServletRequestImpl hreq, HttpSession session, + String location) { + // Is this a valid absolute URL? + URL url = null; + try { + url = new URL(location); + } catch (MalformedURLException e) { + return false; + } + + // Does this URL match down to (and including) the context path? + if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) { + return false; + } + if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) { + return false; + } + int serverPort = hreq.getServerPort(); + if (serverPort == -1) { + if ("https".equals(hreq.getScheme())) { + serverPort = 443; + } else { + serverPort = 80; + } + } + int urlPort = url.getPort(); + if (urlPort == -1) { + if ("https".equals(url.getProtocol())) { + urlPort = 443; + } else { + urlPort = 80; + } + } + if (serverPort != urlPort) { + return false; + } + + String file = url.getFile(); + if (file == null) { + return false; + } + String tok = originalServletContext.getSessionCookieConfig().getName() + "=" + session.getId(); + if (file.indexOf(tok) >= 0) { + return false; + } + + // This URL belongs to our web application, so it is encodeable + return true; + + } + + public long getContentLength() { + return contentLength; + } + + public static enum ResponseState { + NONE, + STREAM, + WRITER + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpSessionImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpSessionImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/HttpSessionImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,233 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionContext; + +import io.undertow.server.session.Session; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.util.IteratorEnumeration; + +/** + * The HTTP session implementation. + * + * Note that for security reasons no attribute names that start with io.undertow are allowed. + * + * @author Stuart Douglas + */ +public class HttpSessionImpl implements HttpSession { + + private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.spec.UNWRAP_HTTP_SESSION"); + + public static final String IO_UNDERTOW = "io.undertow"; + private final Session session; + private final ServletContext servletContext; + private final boolean newSession; + private volatile boolean invalid; + + private HttpSessionImpl(final Session session, final ServletContext servletContext, final boolean newSession) { + this.session = session; + this.servletContext = servletContext; + this.newSession = newSession; + } + + public static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { + // forSession is called by privileged actions only so no need to do it again + ServletRequestContext current = ServletRequestContext.current(); + if (current == null) { + return new HttpSessionImpl(session, servletContext, newSession); + } else { + HttpSessionImpl httpSession = current.getSession(); + if (httpSession == null) { + httpSession = new HttpSessionImpl(session, servletContext, newSession); + current.setSession(httpSession); + } else { + if(httpSession.session != session) { + //in some rare cases it may be that there are two different service contexts involved in the one request + //in this case we just return a new session rather than using the thread local version + httpSession = new HttpSessionImpl(session, servletContext, newSession); + } + } + return httpSession; + } + } + + @Override + public long getCreationTime() { + return session.getCreationTime(); + } + + @Override + public String getId() { + return session.getId(); + } + + @Override + public long getLastAccessedTime() { + return session.getLastAccessedTime(); + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public void setMaxInactiveInterval(final int interval) { + session.setMaxInactiveInterval(interval); + } + + @Override + public int getMaxInactiveInterval() { + return session.getMaxInactiveInterval(); + } + + @Override + public HttpSessionContext getSessionContext() { + return null; + } + + @Override + public Object getAttribute(final String name) { + if(name.startsWith(IO_UNDERTOW)) { + throw new SecurityException(); + } + return session.getAttribute(name); + } + + @Override + public Object getValue(final String name) { + if(name.startsWith(IO_UNDERTOW)) { + throw new SecurityException(); + } + return getAttribute(name); + } + + @Override + public Enumeration getAttributeNames() { + Set attributeNames = getFilteredAttributeNames(); + return new IteratorEnumeration(attributeNames.iterator()); + } + + private Set getFilteredAttributeNames() { + Set attributeNames = new HashSet(session.getAttributeNames()); + Iterator it = attributeNames.iterator(); + while (it.hasNext()) { + if(it.next().startsWith(IO_UNDERTOW)) { + it.remove(); + } + } + return attributeNames; + } + + @Override + public String[] getValueNames() { + Set names = getFilteredAttributeNames(); + String[] ret = new String[names.size()]; + int i = 0; + for (String name : names) { + ret[i++] = name; + } + return ret; + } + + @Override + public void setAttribute(final String name, final Object value) { + if(name.startsWith(IO_UNDERTOW)) { + throw new SecurityException(); + } + if (value == null) { + removeAttribute(name); + } else { + session.setAttribute(name, value); + } + } + + @Override + public void putValue(final String name, final Object value) { + setAttribute(name, value); + } + + @Override + public void removeAttribute(final String name) { + if(name.startsWith(IO_UNDERTOW)) { + throw new SecurityException(); + } + session.removeAttribute(name); + } + + @Override + public void removeValue(final String name) { + removeAttribute(name); + } + + @Override + public void invalidate() { + invalid = true; + ServletRequestContext current = SecurityActions.currentServletRequestContext(); + if (current == null) { + session.invalidate(null); + } else { + session.invalidate(current.getOriginalRequest().getExchange()); + } + } + + @Override + public boolean isNew() { + if (invalid) { + throw UndertowServletMessages.MESSAGES.sessionIsInvalid(); + } + return newSession; + } + + public Session getSession() { + if(System.getSecurityManager() != null) { + AccessController.checkPermission(PERMISSION); + } + return session; + } + + public boolean isInvalid() { + return invalid; + } + + public static class UnwrapSessionAction implements PrivilegedAction { + + private final HttpSessionImpl session; + + public UnwrapSessionAction(HttpSession session) { + this.session = (HttpSessionImpl) session; + } + + @Override + public Session run() { + return session.getSession(); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/PartImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/PartImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/PartImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,134 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.http.Part; + +import io.undertow.server.handlers.form.FormData; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.util.FileUtils; +import io.undertow.util.HeaderValues; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; + +/** + * @author Stuart Douglas + */ +public class PartImpl implements Part { + + private final String name; + private final FormData.FormValue formValue; + private final MultipartConfigElement config; + private final ServletContextImpl servletContext; + + public PartImpl(final String name, final FormData.FormValue formValue, MultipartConfigElement config, ServletContextImpl servletContext) { + this.name = name; + this.formValue = formValue; + this.config = config; + this.servletContext = servletContext; + } + + @Override + public InputStream getInputStream() throws IOException { + if (formValue.isFile()) { + return new BufferedInputStream(new FileInputStream(formValue.getFile())); + } else { + return new ByteArrayInputStream(formValue.getValue().getBytes()); + } + } + + @Override + public String getContentType() { + return formValue.getHeaders().getFirst(Headers.CONTENT_TYPE); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSubmittedFileName() { + return formValue.getFileName(); + } + + @Override + public long getSize() { + if (formValue.isFile()) { + return formValue.getFile().length(); + } else { + return formValue.getValue().length(); + } + } + + @Override + public void write(final String fileName) throws IOException { + File target = new File(fileName); + if(!target.isAbsolute()) { + if(config.getLocation().isEmpty()) { + target = new File(servletContext.getDeployment().getDeploymentInfo().getTempDir(), fileName); + } else { + target = new File(config.getLocation(), fileName); + } + } + if(!formValue.getFile().renameTo(target)) { + //maybe different filesystem + FileUtils.copyFile(formValue.getFile(), target); + } + } + + @Override + public void delete() throws IOException { + if (!formValue.getFile().delete()) { + throw UndertowServletMessages.MESSAGES.deleteFailed(formValue.getFile()); + } + } + + @Override + public String getHeader(final String name) { + return formValue.getHeaders().getFirst(new HttpString(name)); + } + + @Override + public Collection getHeaders(final String name) { + HeaderValues values = formValue.getHeaders().get(new HttpString(name)); + return values == null ? Collections.emptyList() : values; + } + + @Override + public Collection getHeaderNames() { + final Set ret = new HashSet(); + for (HttpString i : formValue.getHeaders().getHeaderNames()) { + ret.add(i.toString()); + } + return ret; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/RequestDispatcherImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/RequestDispatcherImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/RequestDispatcherImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,425 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestWrapper; +import javax.servlet.ServletResponse; +import javax.servlet.ServletResponseWrapper; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.handlers.ServletChain; +import io.undertow.servlet.handlers.ServletPathMatch; +import io.undertow.util.QueryParameterUtils; + +/** + * @author Stuart Douglas + */ +public class RequestDispatcherImpl implements RequestDispatcher { + + private final String path; + private final ServletContextImpl servletContext; + private final ServletChain chain; + private final ServletPathMatch pathMatch; + private final boolean named; + + public RequestDispatcherImpl(final String path, final ServletContextImpl servletContext) { + this.path = path; + this.servletContext = servletContext; + int qPos = path.indexOf("?"); + + if (qPos == -1) { + this.pathMatch = servletContext.getDeployment().getServletPaths().getServletHandlerByPath(path); + } else { + this.pathMatch = servletContext.getDeployment().getServletPaths().getServletHandlerByPath(path.substring(0, qPos)); + } + this.chain = pathMatch.getServletChain(); + this.named = false; + } + + public RequestDispatcherImpl(final ServletChain chain, final ServletContextImpl servletContext) { + this.chain = chain; + this.named = true; + this.servletContext = servletContext; + this.path = null; + this.pathMatch = null; + } + + + @Override + public void forward(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { + final ServletRequestContext servletRequestContext = SecurityActions.requireCurrentServletRequestContext(); + + ThreadSetupAction.Handle handle = null; + ServletContextImpl oldServletContext = null; + HttpSessionImpl oldSession = null; + if (servletRequestContext.getCurrentServletContext() != this.servletContext) { + //cross context request, we need to run the thread setup actions + oldServletContext = servletRequestContext.getCurrentServletContext(); + oldSession = servletRequestContext.getSession(); + servletRequestContext.setSession(null); + handle = this.servletContext.getDeployment().getThreadSetupAction().setup(servletRequestContext.getExchange()); + servletRequestContext.setCurrentServletContext(this.servletContext); + } + + try { + final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); + final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); + if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { + if (servletRequestContext.getOriginalRequest() != request) { + if (!(request instanceof ServletRequestWrapper)) { + throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); + } + } + if (servletRequestContext.getOriginalResponse() != response) { + if (!(response instanceof ServletResponseWrapper)) { + throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); + } + } + } + response.resetBuffer(); + + final ServletRequest oldRequest = servletRequestContext.getServletRequest(); + final ServletResponse oldResponse = servletRequestContext.getServletResponse(); + + Map> queryParameters = requestImpl.getQueryParameters(); + + if (!named) { + + //only update if this is the first forward + if (request.getAttribute(FORWARD_REQUEST_URI) == null) { + requestImpl.setAttribute(FORWARD_REQUEST_URI, requestImpl.getRequestURI()); + requestImpl.setAttribute(FORWARD_CONTEXT_PATH, requestImpl.getContextPath()); + requestImpl.setAttribute(FORWARD_SERVLET_PATH, requestImpl.getServletPath()); + requestImpl.setAttribute(FORWARD_PATH_INFO, requestImpl.getPathInfo()); + requestImpl.setAttribute(FORWARD_QUERY_STRING, requestImpl.getQueryString()); + } + + int qsPos = path.indexOf("?"); + String newServletPath = path; + if (qsPos != -1) { + String newQueryString = newServletPath.substring(qsPos + 1); + newServletPath = newServletPath.substring(0, qsPos); + + Map> newQueryParameters = QueryParameterUtils.mergeQueryParametersWithNewQueryString(queryParameters, newQueryString); + requestImpl.getExchange().setQueryString(newQueryString); + requestImpl.setQueryParameters(newQueryParameters); + } + String newRequestUri = servletContext.getContextPath() + newServletPath; + + + + requestImpl.getExchange().setRelativePath(newServletPath); + requestImpl.getExchange().setRequestPath(newRequestUri); + requestImpl.getExchange().setRequestURI(newRequestUri); + requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(pathMatch); + requestImpl.setServletContext(servletContext); + responseImpl.setServletContext(servletContext); + } + + try { + try { + servletRequestContext.setServletRequest(request); + servletRequestContext.setServletResponse(response); + if (named) { + servletContext.getDeployment().getServletDispatcher().dispatchToServlet(requestImpl.getExchange(), chain, DispatcherType.FORWARD); + } else { + servletContext.getDeployment().getServletDispatcher().dispatchToPath(requestImpl.getExchange(), pathMatch, DispatcherType.FORWARD); + } + + if (!request.isAsyncStarted()) { + if (response instanceof HttpServletResponseImpl) { + responseImpl.closeStreamAndWriter(); + } else { + try { + final PrintWriter writer = response.getWriter(); + writer.flush(); + writer.close(); + } catch (IllegalStateException e) { + final ServletOutputStream outputStream = response.getOutputStream(); + outputStream.flush(); + outputStream.close(); + } + } + } + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } finally { + servletRequestContext.setServletRequest(oldRequest); + servletRequestContext.setServletResponse(oldResponse); + } + } finally { + if (handle != null) { + servletRequestContext.setSession(oldSession); + servletRequestContext.setCurrentServletContext(oldServletContext); + handle.tearDown(); + } + } + } + + + @Override + public void include(final ServletRequest request, final ServletResponse response) throws ServletException, IOException { + final ServletRequestContext servletRequestContext = SecurityActions.requireCurrentServletRequestContext(); + final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); + final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); + ThreadSetupAction.Handle handle = null; + ServletContextImpl oldServletContext = null; + HttpSessionImpl oldSession = null; + if (servletRequestContext.getCurrentServletContext() != this.servletContext) { + //cross context request, we need to run the thread setup actions + oldServletContext = servletRequestContext.getCurrentServletContext(); + oldSession = servletRequestContext.getSession(); + servletRequestContext.setSession(null); + handle = this.servletContext.getDeployment().getThreadSetupAction().setup(servletRequestContext.getExchange()); + servletRequestContext.setCurrentServletContext(this.servletContext); + } + + try { + if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { + if (servletRequestContext.getOriginalRequest() != request) { + if (!(request instanceof ServletRequestWrapper)) { + throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); + } + } + if (servletRequestContext.getOriginalResponse() != response) { + if (!(response instanceof ServletResponseWrapper)) { + throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); + } + } + } + + final ServletRequest oldRequest = servletRequestContext.getServletRequest(); + final ServletResponse oldResponse = servletRequestContext.getServletResponse(); + + Object requestUri = null; + Object contextPath = null; + Object servletPath = null; + Object pathInfo = null; + Object queryString = null; + Map> queryParameters = requestImpl.getQueryParameters(); + + if (!named) { + requestUri = request.getAttribute(INCLUDE_REQUEST_URI); + contextPath = request.getAttribute(INCLUDE_CONTEXT_PATH); + servletPath = request.getAttribute(INCLUDE_SERVLET_PATH); + pathInfo = request.getAttribute(INCLUDE_PATH_INFO); + queryString = request.getAttribute(INCLUDE_QUERY_STRING); + + int qsPos = path.indexOf("?"); + String newServletPath = path; + if (qsPos != -1) { + String newQueryString = newServletPath.substring(qsPos + 1); + newServletPath = newServletPath.substring(0, qsPos); + + Map> newQueryParameters = QueryParameterUtils.mergeQueryParametersWithNewQueryString(queryParameters, newQueryString); + requestImpl.setQueryParameters(newQueryParameters); + requestImpl.setAttribute(INCLUDE_QUERY_STRING, newQueryString); + } else { + requestImpl.setAttribute(INCLUDE_QUERY_STRING, ""); + } + String newRequestUri = servletContext.getContextPath() + newServletPath; + + requestImpl.setAttribute(INCLUDE_REQUEST_URI, newRequestUri); + requestImpl.setAttribute(INCLUDE_CONTEXT_PATH, servletContext.getContextPath()); + requestImpl.setAttribute(INCLUDE_SERVLET_PATH, pathMatch.getMatched()); + requestImpl.setAttribute(INCLUDE_PATH_INFO, pathMatch.getRemaining()); + } + boolean inInclude = responseImpl.isInsideInclude(); + responseImpl.setInsideInclude(true); + DispatcherType oldDispatcherType = servletRequestContext.getDispatcherType(); + + ServletContextImpl oldContext = requestImpl.getServletContext(); + try { + requestImpl.setServletContext(servletContext); + responseImpl.setServletContext(servletContext); + try { + servletRequestContext.setServletRequest(request); + servletRequestContext.setServletResponse(response); + servletContext.getDeployment().getServletDispatcher().dispatchToServlet(requestImpl.getExchange(), chain, DispatcherType.INCLUDE); + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } finally { + responseImpl.setInsideInclude(inInclude); + requestImpl.setServletContext(oldContext); + responseImpl.setServletContext(oldContext); + + servletRequestContext.setServletRequest(oldRequest); + servletRequestContext.setServletResponse(oldResponse); + servletRequestContext.setDispatcherType(oldDispatcherType); + if (!named) { + requestImpl.setAttribute(INCLUDE_REQUEST_URI, requestUri); + requestImpl.setAttribute(INCLUDE_CONTEXT_PATH, contextPath); + requestImpl.setAttribute(INCLUDE_SERVLET_PATH, servletPath); + requestImpl.setAttribute(INCLUDE_PATH_INFO, pathInfo); + requestImpl.setAttribute(INCLUDE_QUERY_STRING, queryString); + requestImpl.setQueryParameters(queryParameters); + } + } + } finally { + if(handle != null) { + servletRequestContext.setSession(oldSession); + servletRequestContext.setCurrentServletContext(oldServletContext); + handle.tearDown(); + } + } + } + + public void error(final ServletRequest request, final ServletResponse response, final String servletName, final String message) throws ServletException, IOException { + error(request, response, servletName, null, message); + } + + public void error(final ServletRequest request, final ServletResponse response, final String servletName) throws ServletException, IOException { + error(request, response, servletName, null, null); + } + + public void error(final ServletRequest request, final ServletResponse response, final String servletName, final Throwable exception) throws ServletException, IOException { + error(request, response, servletName, exception, exception.getMessage()); + } + + private void error(final ServletRequest request, final ServletResponse response, final String servletName, final Throwable exception, final String message) throws ServletException, IOException { + + final ServletRequestContext servletRequestContext = SecurityActions.requireCurrentServletRequestContext(); + if(request.getDispatcherType() == DispatcherType.ERROR) { + //we have already dispatched once with an error + //if we dispatch again we run the risk of a stack overflow + //so we just kill it, the user will just get the basic error page + UndertowServletLogger.REQUEST_LOGGER.errorGeneratingErrorPage(servletRequestContext.getExchange().getRequestPath(), request.getAttribute(ERROR_EXCEPTION), servletRequestContext.getExchange().getResponseCode(), exception); + servletRequestContext.getExchange().endExchange(); + return; + } + + final HttpServletRequestImpl requestImpl = servletRequestContext.getOriginalRequest(); + final HttpServletResponseImpl responseImpl = servletRequestContext.getOriginalResponse(); + if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) { + if (servletRequestContext.getOriginalRequest() != request) { + if (!(request instanceof ServletRequestWrapper)) { + throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(request); + } + } + if (servletRequestContext.getOriginalResponse() != response) { + if (!(response instanceof ServletResponseWrapper)) { + throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(response); + } + } + } + + final ServletRequest oldRequest = servletRequestContext.getServletRequest(); + final ServletResponse oldResponse = servletRequestContext.getServletResponse(); + servletRequestContext.setDispatcherType(DispatcherType.ERROR); + + //only update if this is the first forward + requestImpl.setAttribute(ERROR_REQUEST_URI, requestImpl.getRequestURI()); + requestImpl.setAttribute(ERROR_SERVLET_NAME, servletName); + if (exception != null) { + requestImpl.setAttribute(ERROR_EXCEPTION, exception); + requestImpl.setAttribute(ERROR_EXCEPTION_TYPE, exception.getClass()); + } + requestImpl.setAttribute(ERROR_MESSAGE, message); + requestImpl.setAttribute(ERROR_STATUS_CODE, responseImpl.getStatus()); + + String newQueryString = ""; + int qsPos = path.indexOf("?"); + String newServletPath = path; + if (qsPos != -1) { + newQueryString = newServletPath.substring(qsPos + 1); + newServletPath = newServletPath.substring(0, qsPos); + } + String newRequestUri = servletContext.getContextPath() + newServletPath; + + //todo: a more efficent impl + Map> newQueryParameters = new HashMap>(); + for (String part : newQueryString.split("&")) { + String name = part; + String value = ""; + int equals = part.indexOf('='); + if (equals != -1) { + name = part.substring(0, equals); + value = part.substring(equals + 1); + } + Deque queue = newQueryParameters.get(name); + if (queue == null) { + newQueryParameters.put(name, queue = new ArrayDeque(1)); + } + queue.add(value); + } + requestImpl.setQueryParameters(newQueryParameters); + + requestImpl.getExchange().setRelativePath(newServletPath); + requestImpl.getExchange().setQueryString(newQueryString); + requestImpl.getExchange().setRequestPath(newRequestUri); + requestImpl.getExchange().setRequestURI(newRequestUri); + requestImpl.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY).setServletPathMatch(pathMatch); + requestImpl.setServletContext(servletContext); + responseImpl.setServletContext(servletContext); + + try { + try { + servletRequestContext.setServletRequest(request); + servletRequestContext.setServletResponse(response); + servletContext.getDeployment().getServletDispatcher().dispatchToPath(requestImpl.getExchange(), pathMatch, DispatcherType.ERROR); + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } finally { + servletRequestContext.setServletRequest(oldRequest); + servletRequestContext.setServletResponse(oldResponse); + } + } + + public void mock(ServletRequest request, ServletResponse response) throws ServletException, IOException { + if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + servletContext.getDeployment().getServletDispatcher().dispatchMockRequest(req, resp); + } else { + throw UndertowServletMessages.MESSAGES.invalidRequestResponseType(request, response); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/SecurityActions.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/SecurityActions.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/SecurityActions.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,96 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.servlet.ServletContext; + +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletRequestContext; + + +class SecurityActions { + static ServletRequestContext currentServletRequestContext() { + if (System.getSecurityManager() == null) { + return ServletRequestContext.current(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletRequestContext run() { + return ServletRequestContext.current(); + } + }); + } + } + + static HttpSessionImpl forSession(final Session session, final ServletContext servletContext, final boolean newSession) { + if (System.getSecurityManager() == null) { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public HttpSessionImpl run() { + return HttpSessionImpl.forSession(session, servletContext, newSession); + } + }); + } + } + + static void setCurrentRequestContext(final ServletRequestContext servletRequestContext) { + if (System.getSecurityManager() == null) { + ServletRequestContext.setCurrentRequestContext(servletRequestContext); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + ServletRequestContext.setCurrentRequestContext(servletRequestContext); + return null; + } + }); + } + } + + static void clearCurrentServletAttachments() { + if (System.getSecurityManager() == null) { + ServletRequestContext.clearCurrentServletAttachments(); + } else { + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + ServletRequestContext.clearCurrentServletAttachments(); + return null; + } + }); + } + } + + static ServletRequestContext requireCurrentServletRequestContext() { + if (System.getSecurityManager() == null) { + return ServletRequestContext.requireCurrent(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletRequestContext run() { + return ServletRequestContext.requireCurrent(); + } + }); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletConfigImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletConfigImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletConfigImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,65 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.util.Enumeration; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.util.IteratorEnumeration; + +/** + * @author Stuart Douglas + */ +public class ServletConfigImpl implements ServletConfig { + + private final ServletInfo servletInfo; + private final ServletContext servletContext; + + public ServletConfigImpl(final ServletInfo servletInfo, final ServletContext servletContext) { + this.servletInfo = servletInfo; + this.servletContext = servletContext; + } + + @Override + public String getServletName() { + return servletInfo.getName(); + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(final String name) { + if(name == null) { + throw UndertowServletMessages.MESSAGES.nullName(); + } + return servletInfo.getInitParams().get(name); + } + + @Override + public Enumeration getInitParameterNames() { + return new IteratorEnumeration(servletInfo.getInitParams().keySet().iterator()); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletContextImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletContextImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletContextImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,807 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import io.undertow.Version; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.session.PathParameterSessionConfig; +import io.undertow.server.session.Session; +import io.undertow.server.session.SessionConfig; +import io.undertow.server.session.SessionManager; +import io.undertow.server.session.SslSessionConfig; +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.HttpMethodSecurityInfo; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.ListenerInfo; +import io.undertow.servlet.api.SecurityInfo; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.ServletSecurityInfo; +import io.undertow.servlet.api.SessionConfigWrapper; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.core.ApplicationListeners; +import io.undertow.servlet.core.ManagedListener; +import io.undertow.servlet.handlers.ServletChain; +import io.undertow.servlet.util.EmptyEnumeration; +import io.undertow.servlet.util.ImmediateInstanceFactory; +import io.undertow.servlet.util.IteratorEnumeration; +import io.undertow.util.AttachmentKey; + +import javax.annotation.security.DeclareRoles; +import javax.annotation.security.RunAs; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.MultipartConfigElement; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.SessionTrackingMode; +import javax.servlet.annotation.HttpMethodConstraint; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.annotation.ServletSecurity; +import javax.servlet.descriptor.JspConfigDescriptor; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static io.undertow.servlet.core.ApplicationListeners.ListenerState.NO_LISTENER; +import static io.undertow.servlet.core.ApplicationListeners.ListenerState.PROGRAMATIC_LISTENER; + +/** + * @author Stuart Douglas + */ +public class ServletContextImpl implements ServletContext { + + private final ServletContainer servletContainer; + private final Deployment deployment; + private DeploymentInfo deploymentInfo; + private final ConcurrentMap attributes; + private final SessionCookieConfigImpl sessionCookieConfig; + private final AttachmentKey sessionAttachmentKey = AttachmentKey.create(HttpSessionImpl.class); + private volatile Set sessionTrackingModes = new HashSet(Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE, SessionTrackingMode.URL})); + private volatile Set defaultSessionTrackingModes = new HashSet(Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE, SessionTrackingMode.URL})); + private volatile SessionConfig sessionConfig; + private volatile boolean initialized = false; + + + public ServletContextImpl(final ServletContainer servletContainer, final Deployment deployment) { + this.servletContainer = servletContainer; + this.deployment = deployment; + this.deploymentInfo = deployment.getDeploymentInfo(); + sessionCookieConfig = new SessionCookieConfigImpl(this); + sessionCookieConfig.setPath(deploymentInfo.getContextPath()); + if (deploymentInfo.getServletContextAttributeBackingMap() == null) { + this.attributes = new ConcurrentHashMap(); + } else { + this.attributes = deploymentInfo.getServletContextAttributeBackingMap(); + } + attributes.putAll(deployment.getDeploymentInfo().getServletContextAttributes()); + } + + public void initDone() { + initialized = true; + Set trackingMethods = sessionTrackingModes; + SessionConfig sessionConfig = sessionCookieConfig; + if (trackingMethods != null && !trackingMethods.isEmpty()) { + if (sessionTrackingModes.contains(SessionTrackingMode.SSL)) { + sessionConfig = new SslSessionConfig(deployment.getSessionManager()); + } else { + if (sessionTrackingModes.contains(SessionTrackingMode.COOKIE) && sessionTrackingModes.contains(SessionTrackingMode.URL)) { + sessionCookieConfig.setFallback(new PathParameterSessionConfig(sessionCookieConfig.getName().toLowerCase(Locale.ENGLISH))); + } else if (sessionTrackingModes.contains(SessionTrackingMode.URL)) { + sessionConfig = new PathParameterSessionConfig(sessionCookieConfig.getName().toLowerCase(Locale.ENGLISH)); + } + } + } + SessionConfigWrapper wrapper = deploymentInfo.getSessionConfigWrapper(); + if (wrapper != null) { + sessionConfig = wrapper.wrap(sessionConfig, deployment); + } + this.sessionConfig = sessionConfig; + } + + @Override + public String getContextPath() { + return deploymentInfo.getContextPath(); + } + + @Override + public ServletContext getContext(final String uripath) { + DeploymentManager deploymentByPath = servletContainer.getDeploymentByPath(uripath); + if (deploymentByPath == null) { + return null; + } + return deploymentByPath.getDeployment().getServletContext(); + } + + @Override + public int getMajorVersion() { + return 3; + } + + @Override + public int getMinorVersion() { + return 1; + } + + @Override + public int getEffectiveMajorVersion() { + return deploymentInfo.getMajorVersion(); + } + + @Override + public int getEffectiveMinorVersion() { + return deploymentInfo.getMinorVersion(); + } + + @Override + public String getMimeType(final String file) { + int pos = file.lastIndexOf('.'); + if (pos == -1) { + return deployment.getMimeExtensionMappings().get(file); + } + return deployment.getMimeExtensionMappings().get(file.substring(pos + 1)); + } + + @Override + public Set getResourcePaths(final String path) { + final Resource resource; + try { + resource = deploymentInfo.getResourceManager().getResource(path); + } catch (IOException e) { + return null; + } + if (resource == null || !resource.isDirectory()) { + return null; + } + final Set resources = new HashSet(); + for (Resource res : resource.list()) { + File file = res.getFile(); + if (file != null) { + File base = res.getResourceManagerRoot(); + if (base == null) { + resources.add(file.getPath()); //not much else we can do here + } else { + String filePath = file.getAbsolutePath().substring(base.getAbsolutePath().length()); + filePath = filePath.replace('\\', '/'); //for windows systems + if (file.isDirectory()) { + filePath = filePath + "/"; + } + resources.add(filePath); + } + } + } + return resources; + } + + @Override + public URL getResource(final String path) throws MalformedURLException { + if (!path.startsWith("/")) { + throw UndertowServletMessages.MESSAGES.pathMustStartWithSlash(path); + } + Resource resource = null; + try { + resource = deploymentInfo.getResourceManager().getResource(path); + } catch (IOException e) { + return null; + } + if (resource == null) { + return null; + } + return resource.getUrl(); + } + + @Override + public InputStream getResourceAsStream(final String path) { + Resource resource = null; + try { + resource = deploymentInfo.getResourceManager().getResource(path); + } catch (IOException e) { + return null; + } + if (resource == null) { + return null; + } + try { + if (resource.getFile() != null) { + return new BufferedInputStream(new FileInputStream(resource.getFile())); + } else { + return new BufferedInputStream(resource.getUrl().openStream()); + } + } catch (FileNotFoundException e) { + //should never happen, as the resource loader should return null in this case + return null; + } catch (IOException e) { + return null; + } + } + + @Override + public RequestDispatcher getRequestDispatcher(final String path) { + return new RequestDispatcherImpl(path, this); + } + + @Override + public RequestDispatcher getNamedDispatcher(final String name) { + ServletChain chain = deployment.getServletPaths().getServletHandlerByName(name); + if (chain != null) { + return new RequestDispatcherImpl(chain, this); + } else { + return null; + } + } + + @Override + public Servlet getServlet(final String name) throws ServletException { + return deployment.getServletPaths().getServletHandlerByName(name).getManagedServlet().getServlet().getInstance(); + } + + @Override + public Enumeration getServlets() { + return EmptyEnumeration.instance(); + } + + @Override + public Enumeration getServletNames() { + return EmptyEnumeration.instance(); + } + + @Override + public void log(final String msg) { + UndertowServletLogger.ROOT_LOGGER.info(msg); + } + + @Override + public void log(final Exception exception, final String msg) { + UndertowServletLogger.ROOT_LOGGER.error(msg, exception); + } + + @Override + public void log(final String message, final Throwable throwable) { + UndertowServletLogger.ROOT_LOGGER.error(message, throwable); + } + + @Override + public String getRealPath(final String path) { + if (path == null) { + return null; + } + Resource resource = null; + try { + resource = deploymentInfo.getResourceManager().getResource(path); + } catch (IOException e) { + return null; + } + if (resource == null) { + return null; + } + File file = resource.getFile(); + if (file == null) { + return null; + } + return file.getAbsolutePath(); + } + + @Override + public String getServerInfo() { + return deploymentInfo.getServerName() + " - " + Version.getVersionString(); + } + + @Override + public String getInitParameter(final String name) { + if (name == null) { + throw UndertowServletMessages.MESSAGES.nullName(); + } + return deploymentInfo.getInitParameters().get(name); + } + + @Override + public Enumeration getInitParameterNames() { + return new IteratorEnumeration(deploymentInfo.getInitParameters().keySet().iterator()); + } + + @Override + public boolean setInitParameter(final String name, final String value) { + if (deploymentInfo.getInitParameters().containsKey(name)) { + return false; + } + deploymentInfo.addInitParameter(name, value); + return true; + } + + @Override + public Object getAttribute(final String name) { + return attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + return new IteratorEnumeration(attributes.keySet().iterator()); + } + + @Override + public void setAttribute(final String name, final Object object) { + + if (object == null) { + Object existing = attributes.remove(name); + if (deployment.getApplicationListeners() != null) { + if (existing != null) { + deployment.getApplicationListeners().servletContextAttributeRemoved(name, existing); + } + } + } else { + Object existing = attributes.put(name, object); + if (deployment.getApplicationListeners() != null) { + if (existing != null) { + deployment.getApplicationListeners().servletContextAttributeReplaced(name, existing); + } else { + deployment.getApplicationListeners().servletContextAttributeAdded(name, object); + } + } + } + } + + @Override + public void removeAttribute(final String name) { + Object exiting = attributes.remove(name); + deployment.getApplicationListeners().servletContextAttributeRemoved(name, exiting); + } + + @Override + public String getServletContextName() { + return deploymentInfo.getDisplayName(); + } + + @Override + public ServletRegistration.Dynamic addServlet(final String servletName, final String className) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + try { + if (deploymentInfo.getServlets().containsKey(servletName)) { + return null; + } + ServletInfo servlet = new ServletInfo(servletName, (Class) deploymentInfo.getClassLoader().loadClass(className)); + readServletAnnotations(servlet); + deploymentInfo.addServlet(servlet); + deployment.getServlets().addServlet(servlet); + return new ServletRegistrationImpl(servlet, deployment); + } catch (ClassNotFoundException e) { + throw UndertowServletMessages.MESSAGES.cannotLoadClass(className, e); + } + } + + @Override + public ServletRegistration.Dynamic addServlet(final String servletName, final Servlet servlet) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + if (deploymentInfo.getServlets().containsKey(servletName)) { + return null; + } + ServletInfo s = new ServletInfo(servletName, servlet.getClass(), new ImmediateInstanceFactory(servlet)); + readServletAnnotations(s); + deploymentInfo.addServlet(s); + deployment.getServlets().addServlet(s); + return new ServletRegistrationImpl(s, deployment); + } + + @Override + public ServletRegistration.Dynamic addServlet(final String servletName, final Class servletClass) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + if (deploymentInfo.getServlets().containsKey(servletName)) { + return null; + } + ServletInfo servlet = new ServletInfo(servletName, servletClass); + readServletAnnotations(servlet); + deploymentInfo.addServlet(servlet); + deployment.getServlets().addServlet(servlet); + return new ServletRegistrationImpl(servlet, deployment); + } + + + @Override + public T createServlet(final Class clazz) throws ServletException { + ensureNotProgramaticListener(); + try { + return deploymentInfo.getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); + } + } + + @Override + public ServletRegistration getServletRegistration(final String servletName) { + ensureNotProgramaticListener(); + final ServletInfo servlet = deploymentInfo.getServlets().get(servletName); + if (servlet == null) { + return null; + } + return new ServletRegistrationImpl(servlet, deployment); + } + + @Override + public Map getServletRegistrations() { + ensureNotProgramaticListener(); + final Map ret = new HashMap(); + for (Map.Entry entry : deploymentInfo.getServlets().entrySet()) { + ret.put(entry.getKey(), new ServletRegistrationImpl(entry.getValue(), deployment)); + } + return ret; + } + + @Override + public FilterRegistration.Dynamic addFilter(final String filterName, final String className) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + if (deploymentInfo.getFilters().containsKey(filterName)) { + return null; + } + try { + FilterInfo filter = new FilterInfo(filterName, (Class) deploymentInfo.getClassLoader().loadClass(className)); + deploymentInfo.addFilter(filter); + deployment.getFilters().addFilter(filter); + return new FilterRegistrationImpl(filter, deployment); + } catch (ClassNotFoundException e) { + throw UndertowServletMessages.MESSAGES.cannotLoadClass(className, e); + } + } + + @Override + public FilterRegistration.Dynamic addFilter(final String filterName, final Filter filter) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + + if (deploymentInfo.getFilters().containsKey(filterName)) { + return null; + } + FilterInfo f = new FilterInfo(filterName, filter.getClass(), new ImmediateInstanceFactory(filter)); + deploymentInfo.addFilter(f); + deployment.getFilters().addFilter(f); + return new FilterRegistrationImpl(f, deployment); + + } + + @Override + public FilterRegistration.Dynamic addFilter(final String filterName, final Class filterClass) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + if (deploymentInfo.getFilters().containsKey(filterName)) { + return null; + } + FilterInfo filter = new FilterInfo(filterName, filterClass); + deploymentInfo.addFilter(filter); + deployment.getFilters().addFilter(filter); + return new FilterRegistrationImpl(filter, deployment); + } + + @Override + public T createFilter(final Class clazz) throws ServletException { + ensureNotProgramaticListener(); + try { + return deploymentInfo.getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); + } + } + + @Override + public FilterRegistration getFilterRegistration(final String filterName) { + ensureNotProgramaticListener(); + final FilterInfo filterInfo = deploymentInfo.getFilters().get(filterName); + if (filterInfo == null) { + return null; + } + return new FilterRegistrationImpl(filterInfo, deployment); + } + + @Override + public Map getFilterRegistrations() { + ensureNotProgramaticListener(); + final Map ret = new HashMap(); + for (Map.Entry entry : deploymentInfo.getFilters().entrySet()) { + ret.put(entry.getKey(), new FilterRegistrationImpl(entry.getValue(), deployment)); + } + return ret; + } + + @Override + public SessionCookieConfigImpl getSessionCookieConfig() { + ensureNotProgramaticListener(); + return sessionCookieConfig; + } + + @Override + public void setSessionTrackingModes(final Set sessionTrackingModes) { + ensureNotProgramaticListener(); + ensureNotInitialized(); + if (sessionTrackingModes.size() > 1 && sessionTrackingModes.contains(SessionTrackingMode.SSL)) { + throw UndertowServletMessages.MESSAGES.sslCannotBeCombinedWithAnyOtherMethod(); + } + this.sessionTrackingModes = new HashSet(sessionTrackingModes); + //TODO: actually make this work + } + + @Override + public Set getDefaultSessionTrackingModes() { + ensureNotProgramaticListener(); + return defaultSessionTrackingModes; + } + + @Override + public Set getEffectiveSessionTrackingModes() { + ensureNotProgramaticListener(); + return Collections.unmodifiableSet(sessionTrackingModes); + } + + @Override + public void addListener(final String className) { + try { + Class clazz = (Class) deploymentInfo.getClassLoader().loadClass(className); + addListener(clazz); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public void addListener(final T t) { + ensureNotInitialized(); + ensureNotProgramaticListener(); + if (ApplicationListeners.listenerState() != NO_LISTENER && + ServletContextListener.class.isAssignableFrom(t.getClass())) { + throw UndertowServletMessages.MESSAGES.cannotAddServletContextListener(); + } + ListenerInfo listener = new ListenerInfo(t.getClass(), new ImmediateInstanceFactory(t)); + deploymentInfo.addListener(listener); + deployment.getApplicationListeners().addListener(new ManagedListener(listener, true)); + } + + @Override + public void addListener(final Class listenerClass) { + ensureNotInitialized(); + ensureNotProgramaticListener(); + if (ApplicationListeners.listenerState() != NO_LISTENER && + ServletContextListener.class.isAssignableFrom(listenerClass)) { + throw UndertowServletMessages.MESSAGES.cannotAddServletContextListener(); + } + InstanceFactory factory = null; + try { + factory = deploymentInfo.getClassIntrospecter().createInstanceFactory(listenerClass); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + final ListenerInfo listener = new ListenerInfo(listenerClass, factory); + deploymentInfo.addListener(listener); + deployment.getApplicationListeners().addListener(new ManagedListener(listener, true)); + } + + @Override + public T createListener(final Class clazz) throws ServletException { + ensureNotProgramaticListener(); + if (!ApplicationListeners.isListenerClass(clazz)) { + throw UndertowServletMessages.MESSAGES.listenerMustImplementListenerClass(clazz); + } + try { + return deploymentInfo.getClassIntrospecter().createInstanceFactory(clazz).createInstance().getInstance(); + } catch (Exception e) { + throw UndertowServletMessages.MESSAGES.couldNotInstantiateComponent(clazz.getName(), e); + } + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + return deploymentInfo.getJspConfigDescriptor(); + } + + @Override + public ClassLoader getClassLoader() { + return deploymentInfo.getClassLoader(); + } + + @Override + public void declareRoles(final String... roleNames) { + } + + @Override + public String getVirtualServerName() { + return deployment.getDeploymentInfo().getHostName(); + } + + /** + * Gets the session with the specified ID if it exists + * + * @param sessionId The session ID + * @return The session + */ + public HttpSessionImpl getSession(final String sessionId) { + final SessionManager sessionManager = deployment.getSessionManager(); + Session session = sessionManager.getSession(sessionId); + if (session != null) { + return SecurityActions.forSession(session, this, false); + } + return null; + } + + public HttpSessionImpl getSession(final ServletContextImpl originalServletContext, final HttpServerExchange exchange, boolean create) { + SessionConfig c = originalServletContext.getSessionConfig(); + HttpSessionImpl httpSession = exchange.getAttachment(sessionAttachmentKey); + if (httpSession != null && httpSession.isInvalid()) { + exchange.removeAttachment(sessionAttachmentKey); + httpSession = null; + } + if (httpSession == null) { + final SessionManager sessionManager = deployment.getSessionManager(); + Session session = sessionManager.getSession(exchange, c); + if (session != null) { + httpSession = SecurityActions.forSession(session, this, false); + exchange.putAttachment(sessionAttachmentKey, httpSession); + } else if (create) { + + String existing = c.findSessionId(exchange); + if (originalServletContext != this) { + //this is a cross context request + //we need to make sure there is a top level session + originalServletContext.getSession(originalServletContext, exchange, true); + } else if (existing != null) { + c.clearSession(exchange, existing); + } + + final Session newSession = sessionManager.createSession(exchange, c); + httpSession = SecurityActions.forSession(newSession, this, true); + exchange.putAttachment(sessionAttachmentKey, httpSession); + } + } + return httpSession; + } + + /** + * Gets the session + * + * @param create + * @return + */ + public HttpSessionImpl getSession(final HttpServerExchange exchange, boolean create) { + return getSession(this, exchange, create); + } + + public void updateSessionAccessTime(final HttpServerExchange exchange) { + HttpSessionImpl httpSession = getSession(exchange, false); + if (httpSession != null) { + Session underlyingSession; + if (System.getSecurityManager() == null) { + underlyingSession = httpSession.getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(httpSession)); + } + underlyingSession.requestDone(exchange); + } + } + + public Deployment getDeployment() { + return deployment; + } + + private void ensureNotInitialized() { + if (initialized) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + } + + private void ensureNotProgramaticListener() { + if (ApplicationListeners.listenerState() == PROGRAMATIC_LISTENER) { + throw UndertowServletMessages.MESSAGES.cannotCallFromProgramaticListener(); + } + } + + boolean isInitialized() { + return initialized; + } + + public SessionConfig getSessionConfig() { + return sessionConfig; + } + + public void destroy() { + attributes.clear(); + deploymentInfo = null; + } + + private void readServletAnnotations(ServletInfo servlet) { + if (System.getSecurityManager() == null) { + new ReadServletAnnotationsTask(servlet, deploymentInfo).run(); + } else { + AccessController.doPrivileged(new ReadServletAnnotationsTask(servlet, deploymentInfo)); + } + } + + public void setDefaultSessionTrackingModes(HashSet sessionTrackingModes) { + this.defaultSessionTrackingModes = sessionTrackingModes; + this.sessionTrackingModes = sessionTrackingModes; + } + + private static final class ReadServletAnnotationsTask implements PrivilegedAction { + private final ServletInfo servletInfo; + private final DeploymentInfo deploymentInfo; + + private ReadServletAnnotationsTask(ServletInfo servletInfo, DeploymentInfo deploymentInfo) { + this.servletInfo = servletInfo; + this.deploymentInfo = deploymentInfo; + } + + @Override + public Void run() { + final ServletSecurity security = servletInfo.getServletClass().getAnnotation(ServletSecurity.class); + if (security != null) { + + ServletSecurityInfo servletSecurityInfo = new ServletSecurityInfo() + .setEmptyRoleSemantic(security.value().value() == ServletSecurity.EmptyRoleSemantic.DENY ? SecurityInfo.EmptyRoleSemantic.DENY : SecurityInfo.EmptyRoleSemantic.PERMIT) + .setTransportGuaranteeType(security.value().transportGuarantee() == ServletSecurity.TransportGuarantee.CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) + .addRolesAllowed(security.value().rolesAllowed()); + for (HttpMethodConstraint constraint : security.httpMethodConstraints()) { + servletSecurityInfo.addHttpMethodSecurityInfo(new HttpMethodSecurityInfo() + .setMethod(constraint.value())) + .setEmptyRoleSemantic(constraint.emptyRoleSemantic() == ServletSecurity.EmptyRoleSemantic.DENY ? SecurityInfo.EmptyRoleSemantic.DENY : SecurityInfo.EmptyRoleSemantic.PERMIT) + .setTransportGuaranteeType(constraint.transportGuarantee() == ServletSecurity.TransportGuarantee.CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) + .addRolesAllowed(constraint.rolesAllowed()); + } + servletInfo.setServletSecurityInfo(servletSecurityInfo); + } + final MultipartConfig multipartConfig = servletInfo.getServletClass().getAnnotation(MultipartConfig.class); + if (multipartConfig != null) { + servletInfo.setMultipartConfig(new MultipartConfigElement(multipartConfig.location(), multipartConfig.maxFileSize(), multipartConfig.maxRequestSize(), multipartConfig.fileSizeThreshold())); + } + final RunAs runAs = servletInfo.getServletClass().getAnnotation(RunAs.class); + if (runAs != null) { + servletInfo.setRunAs(runAs.value()); + } + final DeclareRoles declareRoles = servletInfo.getServletClass().getAnnotation(DeclareRoles.class); + if (declareRoles != null) { + deploymentInfo.addSecurityRoles(declareRoles.value()); + } + return null; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletCookieAdaptor.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletCookieAdaptor.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletCookieAdaptor.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,151 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.util.Date; + +import io.undertow.server.handlers.Cookie; +import io.undertow.servlet.UndertowServletMessages; + +/** + * Adaptor between and undertow and a servlet cookie + * + * @author Stuart Douglas + */ +public class ServletCookieAdaptor implements Cookie { + + private final javax.servlet.http.Cookie cookie; + + public ServletCookieAdaptor(final javax.servlet.http.Cookie cookie) { + this.cookie = cookie; + } + + @Override + public String getName() { + return cookie.getName(); + } + + @Override + public String getValue() { + return cookie.getValue(); + } + + @Override + public Cookie setValue(final String value) { + cookie.setValue(value); + return this; + } + + @Override + public String getPath() { + return cookie.getPath(); + } + + @Override + public Cookie setPath(final String path) { + cookie.setPath(path); + return this; + } + + @Override + public String getDomain() { + return cookie.getDomain(); + } + + @Override + public Cookie setDomain(final String domain) { + cookie.setDomain(domain); + return this; + } + + @Override + public Integer getMaxAge() { + return cookie.getMaxAge(); + } + + @Override + public Cookie setMaxAge(final Integer maxAge) { + cookie.setMaxAge(maxAge); + return this; + } + + @Override + public boolean isDiscard() { + return cookie.getMaxAge() < 0; + } + + @Override + public Cookie setDiscard(final boolean discard) { + return this; + } + + @Override + public boolean isSecure() { + return cookie.getSecure(); + } + + @Override + public Cookie setSecure(final boolean secure) { + cookie.setSecure(secure); + return this; + } + + @Override + public int getVersion() { + return cookie.getVersion(); + } + + @Override + public Cookie setVersion(final int version) { + cookie.setVersion(version); + return this; + } + + @Override + public boolean isHttpOnly() { + return cookie.isHttpOnly(); + } + + @Override + public Cookie setHttpOnly(final boolean httpOnly) { + cookie.setHttpOnly(httpOnly); + return this; + } + + @Override + public Date getExpires() { + return null; + } + + @Override + public Cookie setExpires(final Date expires) { + throw UndertowServletMessages.MESSAGES.notImplemented(); + } + + @Override + public String getComment() { + return cookie.getComment(); + } + + @Override + public Cookie setComment(final String comment) { + cookie.setComment(comment); + return this; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletInputStreamImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletInputStreamImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletInputStreamImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,296 @@ +package io.undertow.servlet.spec; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.core.CompositeThreadSetupAction; +import org.xnio.Buffers; +import org.xnio.ChannelListener; +import org.xnio.IoUtils; +import org.xnio.Pool; +import org.xnio.Pooled; +import org.xnio.channels.Channels; +import org.xnio.channels.EmptyStreamSourceChannel; +import org.xnio.channels.StreamSourceChannel; + +import static org.xnio.Bits.allAreClear; +import static org.xnio.Bits.anyAreClear; +import static org.xnio.Bits.anyAreSet; + +/** + * Servlet input stream implementation. This stream is non-buffered, and is used for both + * HTTP requests and for upgraded streams. + * + * @author Stuart Douglas + */ +public class ServletInputStreamImpl extends ServletInputStream { + + private final HttpServletRequestImpl request; + private final StreamSourceChannel channel; + private final Pool bufferPool; + + private volatile ReadListener listener; + + /** + * If this stream is ready for a read + */ + private static final int FLAG_READY = 1; + private static final int FLAG_CLOSED = 1 << 1; + private static final int FLAG_FINISHED = 1 << 2; + private static final int FLAG_ON_DATA_READ_CALLED = 1 << 3; + + private int state; + private AsyncContextImpl asyncContext; + private Pooled pooled; + + public ServletInputStreamImpl(final HttpServletRequestImpl request) { + this.request = request; + if (request.getExchange().isRequestChannelAvailable()) { + this.channel = request.getExchange().getRequestChannel(); + } else { + this.channel = new EmptyStreamSourceChannel(request.getExchange().getIoThread()); + } + this.bufferPool = request.getExchange().getConnection().getBufferPool(); + } + + + @Override + public boolean isFinished() { + return anyAreSet(state, FLAG_FINISHED); + } + + @Override + public boolean isReady() { + return anyAreSet(state, FLAG_READY) && !isFinished(); + } + + @Override + public void setReadListener(final ReadListener readListener) { + if (readListener == null) { + throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); + } + if (listener != null) { + throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); + } + if (!request.isAsyncStarted()) { + throw UndertowServletMessages.MESSAGES.asyncNotStarted(); + } + + asyncContext = request.getAsyncContext(); + listener = readListener; + channel.getReadSetter().set(new ServletInputStreamChannelListener()); + + //we resume from an async task, after the request has been dispatched + asyncContext.addAsyncTask(new Runnable() { + @Override + public void run() { + channel.wakeupReads(); + } + }); + } + + @Override + public int read() throws IOException { + byte[] b = new byte[1]; + int read = read(b); + if (read == -1) { + return -1; + } + return b[0] & 0xff; + } + + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + if(listener != null) { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + } else { + readIntoBuffer(); + } + if (anyAreSet(state, FLAG_FINISHED)) { + return -1; + } + if (len == 0) { + return 0; + } + ByteBuffer buffer = pooled.getResource(); + int copied = Buffers.copy(ByteBuffer.wrap(b, off, len), buffer); + if (!buffer.hasRemaining()) { + pooled.free(); + pooled = null; + if(listener != null) { + readIntoBufferNonBlocking(); + } + } + return copied; + } + + private void readIntoBuffer() throws IOException { + if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { + pooled = bufferPool.allocate(); + + int res = Channels.readBlocking(channel, pooled.getResource()); + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } + } + } + + private void readIntoBufferNonBlocking() throws IOException { + if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { + pooled = bufferPool.allocate(); + if (listener == null) { + int res = channel.read(pooled.getResource()); + if (res == 0) { + pooled.free(); + pooled = null; + return; + } + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } + } else { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + int res = channel.read(pooled.getResource()); + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } else if (res == 0) { + state &= ~FLAG_READY; + pooled.free(); + pooled = null; + asyncContext.addAsyncTask(new Runnable() { + @Override + public void run() { + channel.resumeReads(); + } + }); + } + } + } + } + + @Override + public int available() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + readIntoBufferNonBlocking(); + if (anyAreSet(state, FLAG_FINISHED)) { + return 0; + } + if (pooled == null) { + return 0; + } + return pooled.getResource().remaining(); + } + + @Override + public void close() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + return; + } + while (allAreClear(state, FLAG_FINISHED)) { + readIntoBuffer(); + if (pooled != null) { + pooled.free(); + pooled = null; + } + } + if (pooled != null) { + pooled.free(); + pooled = null; + } + channel.shutdownReads(); + state |= FLAG_FINISHED | FLAG_CLOSED; + } + + private class ServletInputStreamChannelListener implements ChannelListener { + @Override + public void handleEvent(final StreamSourceChannel channel) { + channel.suspendReads(); + asyncContext.addAsyncTask(new Runnable() { + @Override + public void run() { + if (asyncContext.isDispatched()) { + //this is no longer an async request + //we just return + //TODO: what do we do here? Revert back to blocking mode? + return; + } + if (anyAreSet(state, FLAG_FINISHED)) { + return; + } + state |= FLAG_READY; + try { + readIntoBufferNonBlocking(); + if(pooled != null) { + state |= FLAG_READY; + if (!anyAreSet(state, FLAG_FINISHED)) { + CompositeThreadSetupAction action = request.getServletContext().getDeployment().getThreadSetupAction(); + ThreadSetupAction.Handle handle = action.setup(request.getExchange()); + try { + listener.onDataAvailable(); + } finally { + handle.tearDown(); + } + } + } + } catch (Exception e) { + CompositeThreadSetupAction action = request.getServletContext().getDeployment().getThreadSetupAction(); + ThreadSetupAction.Handle handle = action.setup(request.getExchange()); + try { + listener.onError(e); + } finally { + handle.tearDown(); + } + IoUtils.safeClose(channel); + } + if (anyAreSet(state, FLAG_FINISHED)) { + if (anyAreClear(state, FLAG_ON_DATA_READ_CALLED)) { + try { + state |= FLAG_ON_DATA_READ_CALLED; + channel.shutdownReads(); + CompositeThreadSetupAction action = request.getServletContext().getDeployment().getThreadSetupAction(); + ThreadSetupAction.Handle handle = action.setup(request.getExchange()); + try { + listener.onAllDataRead(); + } finally { + handle.tearDown(); + } + } catch (IOException e) { + listener.onError(e); + IoUtils.safeClose(channel); + } + } + } + } + }); + + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletOutputStreamImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletOutputStreamImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletOutputStreamImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,901 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import io.undertow.io.BufferWritableOutputStream; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.servlet.api.ThreadSetupAction; +import io.undertow.servlet.core.CompositeThreadSetupAction; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.util.Headers; +import org.xnio.Buffers; +import org.xnio.ChannelListener; +import org.xnio.ChannelListeners; +import org.xnio.IoUtils; +import org.xnio.Pool; +import org.xnio.Pooled; +import org.xnio.channels.Channels; +import org.xnio.channels.StreamSinkChannel; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.WriteListener; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import static org.xnio.Bits.allAreClear; +import static org.xnio.Bits.anyAreClear; +import static org.xnio.Bits.anyAreSet; + +/** + * This stream essentially has two modes. When it is being used in standard blocking mode then + * it will buffer in the pooled buffer. If the stream is closed before the buffer is full it will + * set a content-length header if one has not been explicitly set. + *

+ * If a content-length header was present when the stream was created then it will automatically + * close and flush itself once the appropriate amount of data has been written. + *

+ * Once the listener has been set it goes into async mode, and writes become non blocking. Most methods + * have two different code paths, based on if the listener has been set or not + *

+ * Once the write listener has been set operations must only be invoked on this stream from the write + * listener callback. Attempting to invoke from a different thread will result in an IllegalStateException. + *

+ * Async listener tasks are queued in the {@link AsyncContextImpl}. At most one lister can be active at + * one time, which simplifies the thread safety requirements. + * + * @author Stuart Douglas + */ +public class ServletOutputStreamImpl extends ServletOutputStream implements BufferWritableOutputStream { + + private final ServletRequestContext servletRequestContext; + private Pooled pooledBuffer; + private ByteBuffer buffer; + private Integer bufferSize; + private StreamSinkChannel channel; + private long written; + private int state; + private AsyncContextImpl asyncContext; + + private WriteListener listener; + private WriteChannelListener internalListener; + + + /** + * buffers that are queued up to be written via async writes. This will include + * {@link #buffer} as the first element, and maybe a user supplied buffer that + * did not fit + */ + private ByteBuffer[] buffersToWrite; + + private FileChannel pendingFile; + + private static final int FLAG_CLOSED = 1; + private static final int FLAG_WRITE_STARTED = 1 << 1; + private static final int FLAG_READY = 1 << 2; + private static final int FLAG_DELEGATE_SHUTDOWN = 1 << 3; + private static final int FLAG_IN_CALLBACK = 1 << 4; + + //TODO: should this be configurable? + private static final int MAX_BUFFERS_TO_ALLOCATE = 6; + + private CompositeThreadSetupAction threadSetupAction; + + /** + * Construct a new instance. No write timeout is configured. + */ + public ServletOutputStreamImpl(final ServletRequestContext servletRequestContext) { + this.threadSetupAction = servletRequestContext.getDeployment().getThreadSetupAction(); + this.servletRequestContext = servletRequestContext; + } + + /** + * Construct a new instance. No write timeout is configured. + */ + public ServletOutputStreamImpl(final ServletRequestContext servletRequestContext, int bufferSize) { + this.bufferSize = bufferSize; + this.servletRequestContext = servletRequestContext; + } + + /** + * {@inheritDoc} + */ + public void write(final int b) throws IOException { + write(new byte[]{(byte) b}, 0, 1); + } + + /** + * {@inheritDoc} + */ + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + /** + * {@inheritDoc} + */ + public void write(final byte[] b, final int off, final int len) throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + if (len < 1) { + return; + } + + if (listener == null) { + ByteBuffer buffer = buffer(); + if (buffer.remaining() < len) { + + //so what we have will not fit. + //We allocate multiple buffers up to MAX_BUFFERS_TO_ALLOCATE + //and put it in them + //if it still dopes not fit we loop, re-using these buffers + + StreamSinkChannel channel = this.channel; + if (channel == null) { + this.channel = channel = servletRequestContext.getExchange().getResponseChannel(); + } + final Pool bufferPool = servletRequestContext.getExchange().getConnection().getBufferPool(); + ByteBuffer[] buffers = new ByteBuffer[MAX_BUFFERS_TO_ALLOCATE + 1]; + Pooled[] pooledBuffers = new Pooled[MAX_BUFFERS_TO_ALLOCATE]; + try { + buffers[0] = buffer; + int bytesWritten = 0; + int rem = buffer.remaining(); + buffer.put(b, bytesWritten + off, rem); + buffer.flip(); + bytesWritten += rem; + int bufferCount = 1; + for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE; ++i) { + Pooled pooled = bufferPool.allocate(); + pooledBuffers[bufferCount - 1] = pooled; + buffers[bufferCount++] = pooled.getResource(); + ByteBuffer cb = pooled.getResource(); + int toWrite = len - bytesWritten; + if (toWrite > cb.remaining()) { + rem = cb.remaining(); + cb.put(b, bytesWritten + off, rem); + cb.flip(); + bytesWritten += rem; + } else { + cb.put(b, bytesWritten + off, toWrite); + bytesWritten = len; + cb.flip(); + break; + } + } + Channels.writeBlocking(channel, buffers, 0, bufferCount); + while (bytesWritten < len) { + //ok, it did not fit, loop and loop and loop until it is done + bufferCount = 0; + for (int i = 0; i < MAX_BUFFERS_TO_ALLOCATE + 1; ++i) { + ByteBuffer cb = buffers[i]; + cb.clear(); + bufferCount++; + int toWrite = len - bytesWritten; + if (toWrite > cb.remaining()) { + rem = cb.remaining(); + cb.put(b, bytesWritten + off, rem); + cb.flip(); + bytesWritten += rem; + } else { + cb.put(b, bytesWritten + off, toWrite); + bytesWritten = len; + cb.flip(); + break; + } + } + Channels.writeBlocking(channel, buffers, 0, bufferCount); + } + buffer.clear(); + } finally { + for (int i = 0; i < pooledBuffers.length; ++i) { + Pooled p = pooledBuffers[i]; + if (p == null) { + break; + } + p.free(); + } + } + } else { + buffer.put(b, off, len); + if (buffer.remaining() == 0) { + writeBufferBlocking(false); + } + } + updateWritten(len); + } else { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + //even though we are in async mode we are still buffering + try { + ByteBuffer buffer = buffer(); + if (buffer.remaining() > len) { + buffer.put(b, off, len); + } else { + buffer.flip(); + final ByteBuffer userBuffer = ByteBuffer.wrap(b, off, len); + final ByteBuffer[] bufs = new ByteBuffer[]{buffer, userBuffer}; + long toWrite = Buffers.remaining(bufs); + long res; + long written = 0; + createChannel(); + state |= FLAG_WRITE_STARTED; + do { + res = channel.write(bufs); + written += res; + if (res == 0) { + //write it out with a listener + //but we need to copy any extra data + final ByteBuffer copy = ByteBuffer.allocate(userBuffer.remaining()); + copy.put(userBuffer); + copy.flip(); + + this.buffersToWrite = new ByteBuffer[]{buffer, copy}; + state &= ~FLAG_READY; + resumeWrites(); + return; + } + } while (written < toWrite); + buffer.clear(); + } + } finally { + updateWrittenAsync(len); + } + } + } + + + @Override + public void write(ByteBuffer[] buffers) throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + int len = 0; + for (ByteBuffer buf : buffers) { + len += buf.remaining(); + } + if (len < 1) { + return; + } + + if (listener == null) { + //if we have received the exact amount of content write it out in one go + //this is a common case when writing directly from a buffer cache. + if (this.written == 0 && len == servletRequestContext.getOriginalResponse().getContentLength()) { + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + Channels.writeBlocking(channel, buffers, 0, buffers.length); + state |= FLAG_WRITE_STARTED; + } else { + ByteBuffer buffer = buffer(); + if (len < buffer.remaining()) { + Buffers.copy(buffer, buffers, 0, buffers.length); + } else { + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + if (buffer.position() == 0) { + Channels.writeBlocking(channel, buffers, 0, buffers.length); + } else { + final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length + 1]; + buffer.flip(); + newBuffers[0] = buffer; + System.arraycopy(buffers, 0, newBuffers, 1, buffers.length); + Channels.writeBlocking(channel, newBuffers, 0, newBuffers.length); + buffer.clear(); + } + state |= FLAG_WRITE_STARTED; + } + } + + updateWritten(len); + } else { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + //even though we are in async mode we are still buffering + try { + ByteBuffer buffer = buffer(); + if (buffer.remaining() > len) { + Buffers.copy(buffer, buffers, 0, buffers.length); + } else { + final ByteBuffer[] bufs = new ByteBuffer[buffers.length + 1]; + buffer.flip(); + bufs[0] = buffer; + System.arraycopy(buffers, 0, bufs, 1, buffers.length); + long toWrite = Buffers.remaining(bufs); + long res; + long written = 0; + createChannel(); + state |= FLAG_WRITE_STARTED; + do { + res = channel.write(bufs); + written += res; + if (res == 0) { + //write it out with a listener + //but we need to copy any extra data + //TODO: should really allocate from the pool here + final ByteBuffer copy = ByteBuffer.allocate((int) Buffers.remaining(buffers)); + Buffers.copy(copy, buffers, 0, buffers.length); + copy.flip(); + this.buffersToWrite = new ByteBuffer[]{buffer, copy}; + state &= ~FLAG_READY; + resumeWrites(); + return; + } + } while (written < toWrite); + buffer.clear(); + } + } finally { + updateWrittenAsync(len); + } + } + } + + @Override + public void write(ByteBuffer byteBuffer) throws IOException { + write(new ByteBuffer[]{byteBuffer}); + } + + void updateWritten(final long len) throws IOException { + this.written += len; + long contentLength = servletRequestContext.getOriginalResponse().getContentLength(); + if (contentLength != -1 && this.written >= contentLength) { + close(); + } + } + + void updateWrittenAsync(final long len) throws IOException { + this.written += len; + long contentLength = servletRequestContext.getOriginalResponse().getContentLength(); + if (contentLength != -1 && this.written >= contentLength) { + state |= FLAG_CLOSED; + //if buffersToWrite is set we are already flushing + //so we don't have to do anything + if (buffersToWrite == null && pendingFile == null) { + if (flushBufferAsync(true)) { + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + if (!channel.flush()) { + resumeWrites(); + } + } else { + resumeWrites(); + } + } + } + } + + private void resumeWrites() { + if (anyAreSet(state, FLAG_IN_CALLBACK)) { + //writes will be resumed at the end of the callback + return; + } + if (channel != null) { + channel.getWriteSetter().set(internalListener); + channel.resumeWrites(); + } else { + servletRequestContext.getExchange().getIoThread().execute(new Runnable() { + @Override + public void run() { + ChannelListeners.invokeChannelListener(null, internalListener); + } + }); + } + } + + private boolean flushBufferAsync(final boolean writeFinal) throws IOException { + ByteBuffer[] bufs = buffersToWrite; + if (bufs == null) { + ByteBuffer buffer = this.buffer; + if (buffer == null || buffer.position() == 0) { + return true; + } + buffer.flip(); + bufs = new ByteBuffer[]{buffer}; + } + long toWrite = Buffers.remaining(bufs); + if (toWrite == 0) { + //we clear the buffer, so it can be written to again + buffer.clear(); + return true; + } + state |= FLAG_WRITE_STARTED; + createChannel(); + long res; + long written = 0; + do { + if(writeFinal) { + res = channel.writeFinal(bufs); + } else { + res = channel.write(bufs); + } + written += res; + if (res == 0) { + //write it out with a listener + state = state & ~FLAG_READY; + buffersToWrite = bufs; + return false; + } + } while (written < toWrite); + buffer.clear(); + return true; + } + + + /** + * Returns the underlying buffer. If this has not been created yet then + * it is created. + *

+ * Callers that use this method must call {@link #updateWritten(long)} to update the written + * amount. + *

+ * This allows the buffer to be filled directly, which can be more efficient. + *

+ * This method is basically a hack that should only be used by the print writer + * + * @return The underlying buffer + */ + ByteBuffer underlyingBuffer() { + if(anyAreSet(state, FLAG_CLOSED)) { + return null; + } + return buffer(); + } + + /** + * {@inheritDoc} + */ + public void flush() throws IOException { + //according to the servlet spec we ignore a flush from within an include + if (servletRequestContext.getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE) { + return; + } + if(servletRequestContext.getDeployment().getDeploymentInfo().isIgnoreFlush() && + servletRequestContext.getExchange().isRequestComplete() && + servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null) { + //we mark the stream as flushed, but don't actually flush + //because in most cases flush just kills performance + //we only do this if the request is fully read, so that http tunneling scenarios still work + servletRequestContext.getOriginalResponse().setIgnoredFlushPerformed(true); + return; + } + flushInternal(); + } + + /** + * {@inheritDoc} + */ + public void flushInternal() throws IOException { + if (listener == null) { + if (anyAreSet(state, FLAG_CLOSED)) { + //just return + return; + } + if (buffer != null && buffer.position() != 0) { + writeBufferBlocking(false); + } + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + Channels.flushBlocking(channel); + } else { + if (anyAreClear(state, FLAG_READY)) { + return; + } + createChannel(); + if (buffer == null || buffer.position() == 0) { + //nothing to flush, we just flush the underlying stream + //it does not matter if this succeeds or not + channel.flush(); + return; + } + //we have some data in the buffer, we can just write it out + //if the write fails we just compact, rather than changing the ready state + state |= FLAG_WRITE_STARTED; + buffer.flip(); + long res; + do { + res = channel.write(buffer); + written += res; + } while (buffer.hasRemaining() && res != 0); + if (!buffer.hasRemaining()) { + channel.flush(); + } + buffer.compact(); + } + } + + @Override + public void transferFrom(FileChannel source) throws IOException { + if (listener == null) { + if (anyAreSet(state, FLAG_CLOSED)) { + //just return + return; + } + if (buffer != null && buffer.position() != 0) { + writeBufferBlocking(false); + } + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + long position = source.position(); + long count = source.size() - position; + Channels.transferBlocking(channel, source, position, count); + updateWritten(count); + } else { + state |= FLAG_WRITE_STARTED; + createChannel(); + + long pos = 0; + try { + long size = source.size(); + pos = source.position(); + + while (size - pos > 0) { + long ret = channel.transferFrom(pendingFile, pos, size - pos); + if (ret <= 0) { + state &= ~FLAG_READY; + pendingFile = source; + source.position(pos); + resumeWrites(); + return; + } + pos += ret; + } + } finally { + updateWrittenAsync(pos - source.position()); + } + } + + } + + + private void writeBufferBlocking(final boolean writeFinal) throws IOException { + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + buffer.flip(); + while (buffer.hasRemaining()) { + if(writeFinal) { + channel.writeFinal(buffer); + } else { + channel.write(buffer); + } + if(buffer.hasRemaining()) { + channel.awaitWritable(); + } + } + buffer.clear(); + state |= FLAG_WRITE_STARTED; + } + + /** + * {@inheritDoc} + */ + public void close() throws IOException { + if (servletRequestContext.getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE) { + return; + } + if (listener == null) { + if (anyAreSet(state, FLAG_CLOSED)) return; + state |= FLAG_CLOSED; + state &= ~FLAG_READY; + if (allAreClear(state, FLAG_WRITE_STARTED) && channel == null && servletRequestContext.getOriginalResponse().getHeader(Headers.CONTENT_LENGTH_STRING) == null) { + if(servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null) { + if (buffer == null) { + servletRequestContext.getExchange().getResponseHeaders().put(Headers.CONTENT_LENGTH, "0"); + } else { + servletRequestContext.getExchange().getResponseHeaders().put(Headers.CONTENT_LENGTH, Integer.toString(buffer.position())); + } + } + } + try { + if (buffer != null) { + writeBufferBlocking(true); + } + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + } + state |= FLAG_DELEGATE_SHUTDOWN; + StreamSinkChannel channel = this.channel; + if(channel != null) { //mock requests + channel.shutdownWrites(); + Channels.flushBlocking(channel); + } + } finally { + if (pooledBuffer != null) { + pooledBuffer.free(); + buffer = null; + } else { + buffer = null; + } + } + } else { + closeAsync(); + } + } + + /** + * Closes the channel, and flushes any data out using async IO + *

+ * This is used in two situations, if an output stream is not closed when a + * request is done, and when performing a close on a stream that is in async + * mode + * + * @throws IOException + */ + public void closeAsync() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) return; + + state |= FLAG_CLOSED; + state &= ~FLAG_READY; + if (allAreClear(state, FLAG_WRITE_STARTED) && channel == null) { + + if(servletRequestContext.getOriginalResponse().getHeader(Headers.TRANSFER_ENCODING_STRING) == null) { + if (buffer == null) { + servletRequestContext.getOriginalResponse().setHeader(Headers.CONTENT_LENGTH, "0"); + } else { + servletRequestContext.getOriginalResponse().setHeader(Headers.CONTENT_LENGTH, Integer.toString(buffer.position())); + } + } + } + createChannel(); + if (buffer != null) { + if (!flushBufferAsync(true)) { + resumeWrites(); + return; + } + + if (pooledBuffer != null) { + pooledBuffer.free(); + buffer = null; + } else { + buffer = null; + } + } + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + if (!channel.flush()) { + resumeWrites(); + } + } + + private void createChannel() { + if (channel == null) { + channel = servletRequestContext.getExchange().getResponseChannel(); + channel.getWriteSetter().set(internalListener); + } + } + + + private ByteBuffer buffer() { + ByteBuffer buffer = this.buffer; + if (buffer != null) { + return buffer; + } + if (bufferSize != null) { + this.buffer = ByteBuffer.allocateDirect(bufferSize); + return this.buffer; + } else { + this.pooledBuffer = servletRequestContext.getExchange().getConnection().getBufferPool().allocate(); + this.buffer = pooledBuffer.getResource(); + return this.buffer; + } + } + + public void resetBuffer() { + if (allAreClear(state, FLAG_WRITE_STARTED)) { + if (pooledBuffer != null) { + pooledBuffer.free(); + pooledBuffer = null; + } + buffer = null; + } else { + throw UndertowServletMessages.MESSAGES.responseAlreadyCommited(); + } + } + + public void setBufferSize(final int size) { + if (buffer != null) { + throw UndertowServletMessages.MESSAGES.contentHasBeenWritten(); + } + this.bufferSize = size; + } + + public boolean isClosed() { + return anyAreSet(state, FLAG_CLOSED); + } + + @Override + public boolean isReady() { + if (listener == null) { + //TODO: is this the correct behaviour? + throw UndertowServletMessages.MESSAGES.streamNotInAsyncMode(); + } + return anyAreSet(state, FLAG_READY); + } + + @Override + public void setWriteListener(final WriteListener writeListener) { + if (writeListener == null) { + throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); + } + if (listener != null) { + throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); + } + final ServletRequest servletRequest = servletRequestContext.getServletRequest(); + if (!servletRequest.isAsyncStarted()) { + throw UndertowServletMessages.MESSAGES.asyncNotStarted(); + } + asyncContext = (AsyncContextImpl) servletRequest.getAsyncContext(); + listener = writeListener; + //we register the write listener on the underlying connection + //so we don't have to force the creation of the response channel + //under normal circumstances this will break write listener delegation + this.internalListener = new WriteChannelListener(); + //we resume from an async task, after the request has been dispatched + internalListener.handleEvent(null); + } + + ServletRequestContext getServletRequestContext() { + return servletRequestContext; + } + + + private class WriteChannelListener implements ChannelListener { + + @Override + public void handleEvent(final StreamSinkChannel aChannel) { + if (channel != null) { + channel.suspendWrites(); + } + //we run this whole thing as a async task, to avoid threading issues + asyncContext.addAsyncTask(new Runnable() { + @Override + public void run() { + //flush the channel if it is closed + if (anyAreSet(state, FLAG_DELEGATE_SHUTDOWN)) { + try { + //either it will work, and the channel is closed + //or it won't, and we continue with writes resumed + if (!channel.flush()) { + resumeWrites(); + } + return; + } catch (IOException e) { + handleError(e); + return; + } + } + //if there is data still to write + if (buffersToWrite != null) { + long toWrite = Buffers.remaining(buffersToWrite); + long written = 0; + long res; + do { + try { + res = channel.write(buffersToWrite); + written += res; + if (res == 0) { + resumeWrites(); + return; + } + } catch (IOException e) { + handleError(e); + return; + } + } while (written < toWrite); + buffersToWrite = null; + } + if (pendingFile != null) { + try { + long size = pendingFile.size(); + long pos = pendingFile.position(); + + while (size - pos > 0) { + long ret = channel.transferFrom(pendingFile, pos, size - pos); + if (ret <= 0) { + pendingFile.position(pos); + resumeWrites(); + return; + } + pos += ret; + } + pendingFile = null; + } catch (IOException e) { + handleError(e); + return; + } + } + if (anyAreSet(state, FLAG_CLOSED)) { + try { + + if (pooledBuffer != null) { + pooledBuffer.free(); + buffer = null; + } else { + buffer = null; + } + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + if (!channel.flush()) { + resumeWrites(); + } + } catch (IOException e) { + handleError(e); + return; + } + } else { + + + if (asyncContext.isDispatched()) { + //this is no longer an async request + //we just return for now + //TODO: what do we do here? Revert back to blocking mode? + return; + } + + state |= FLAG_READY; + try { + state |= FLAG_IN_CALLBACK; + + ThreadSetupAction.Handle handle = threadSetupAction.setup(servletRequestContext.getExchange()); + try { + listener.onWritePossible(); + } finally { + handle.tearDown(); + } + if (!isReady()) { + //if the stream is still ready then we do not resume writes + //this is per spec, we only call the listener once for each time + //isReady returns true + state &= ~FLAG_IN_CALLBACK; + resumeWrites(); + } + } catch (Throwable e) { + IoUtils.safeClose(channel); + } finally { + state &= ~FLAG_IN_CALLBACK; + } + } + } + }); + } + + private void handleError(final IOException e) { + try { + ThreadSetupAction.Handle handle = threadSetupAction.setup(servletRequestContext.getExchange()); + try { + listener.onError(e); + } finally { + handle.tearDown(); + } + } finally { + IoUtils.safeClose(channel, servletRequestContext.getExchange().getConnection()); + } + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriter.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriter.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriter.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,348 @@ +package io.undertow.servlet.spec; + +import javax.servlet.DispatcherType; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; +import java.util.Locale; + +/** + * Real servlet print writer functionality, that is not limited by extending + * {@link java.io.PrintWriter} + *

+ * + * @author Stuart Douglas + */ +public class ServletPrintWriter { + + private static final char[] EMPTY_CHAR = {}; + + private final ServletOutputStreamImpl outputStream; + private final String charset; + private CharsetEncoder charsetEncoder; + private boolean error = false; + private boolean closed = false; + private char[] underflow; + + public ServletPrintWriter(final ServletOutputStreamImpl outputStream, final String charset) throws UnsupportedEncodingException { + this.charset = charset; + this.outputStream = outputStream; + + //for some known charset we get optimistic and hope that + //only ascii will be output + //in this case we can avoid creating the encoder altogether + if (!charset.equalsIgnoreCase("utf-8") && + !charset.equalsIgnoreCase("iso-8859-1")) { + createEncoder(); + } + } + + private void createEncoder() { + this.charsetEncoder = Charset.forName(this.charset).newEncoder(); + //replace malformed and unmappable with question marks + this.charsetEncoder.onUnmappableCharacter(CodingErrorAction.REPLACE); + this.charsetEncoder.onMalformedInput(CodingErrorAction.REPLACE); + } + + public void flush() { + try { + outputStream.flush(); + } catch (IOException e) { + error = true; + } + } + + public void close() { + + if (outputStream.getServletRequestContext().getOriginalRequest().getDispatcherType() == DispatcherType.INCLUDE) { + return; + } + if (closed) { + return; + } + closed = true; + try { + boolean done = false; + CharBuffer buffer; + if (underflow == null) { + buffer = CharBuffer.wrap(EMPTY_CHAR); + } else { + buffer = CharBuffer.wrap(underflow); + underflow = null; + } + if (charsetEncoder != null) { + do { + ByteBuffer out = outputStream.underlyingBuffer(); + if (out == null) { + //servlet output stream has already been closed + error = true; + return; + } + CoderResult result = charsetEncoder.encode(buffer, out, true); + if (result.isOverflow()) { + outputStream.flushInternal(); + if (out.remaining() == 0) { + outputStream.close(); + error = true; + return; + } + } else { + done = true; + } + } while (!done); + } + outputStream.close(); + } catch (IOException e) { + error = true; + } + } + + public boolean checkError() { + return error; + } + + public void write(final CharBuffer input) { + ByteBuffer buffer = outputStream.underlyingBuffer(); + if (buffer == null) { + //stream has been closed + error = true; + return; + } + try { + if (!buffer.hasRemaining()) { + outputStream.flushInternal(); + if (!buffer.hasRemaining()) { + error = true; + return; + } + } + + if (charsetEncoder == null) { + //fast path, basically we are hoping this is ascii only + int remaining = buffer.remaining(); + boolean ok = true; + //so we have a pure ascii buffer, just write it out and skip all the encoder cost + while (input.hasRemaining()) { + if (!buffer.hasRemaining()) { + outputStream.flushInternal(); + } + char c = input.get(); + if (c > 127) { + ok = false; + input.position(input.position() - 1); //push the character back + break; + } + buffer.put((byte) c); + } + outputStream.updateWritten(remaining - buffer.remaining()); + if (ok) { + return; + } + createEncoder(); + } + final CharBuffer cb; + if (underflow == null) { + cb = input; + } else { + char[] newArray = new char[underflow.length + input.remaining()]; + System.arraycopy(underflow, 0, newArray, 0, underflow.length); + input.get(newArray, underflow.length, input.remaining()); + cb = CharBuffer.wrap(newArray); + underflow = null; + } + int last = -1; + while (cb.hasRemaining()) { + int remaining = buffer.remaining(); + CoderResult result = charsetEncoder.encode(cb, buffer, false); + outputStream.updateWritten(remaining - buffer.remaining()); + if (result.isOverflow() || !buffer.hasRemaining()) { + outputStream.flushInternal(); + if (!buffer.hasRemaining()) { + error = true; + return; + } + } + if (result.isUnderflow()) { + underflow = new char[cb.remaining()]; + cb.get(underflow); + return; + } + if (result.isError()) { + error = true; + return; + } + if (result.isUnmappable()) { + //this should not happen + error = true; + return; + } + if (last == cb.remaining()) { + underflow = new char[cb.remaining()]; + cb.get(underflow); + return; + } + last = cb.remaining(); + } + } catch (IOException e) { + error = true; + } + } + + public void write(final int c) { + final CharBuffer cb = CharBuffer.wrap(Character.toString((char) c)); + write(cb); + } + + public void write(final char[] buf, final int off, final int len) { + final CharBuffer cb = CharBuffer.wrap(buf, off, len); + write(cb); + } + + public void write(final char[] buf) { + final CharBuffer cb = CharBuffer.wrap(buf); + write(cb); + } + + public void write(final String s, final int off, final int len) { + final CharBuffer cb = CharBuffer.wrap(s, off, off + len); + write(cb); + } + + public void write(final String s) { + final CharBuffer cb = CharBuffer.wrap(s); + write(cb); + } + + public void print(final boolean b) { + final CharBuffer cb = CharBuffer.wrap(Boolean.toString(b)); + write(cb); + } + + public void print(final char c) { + final CharBuffer cb = CharBuffer.wrap(Character.toString(c)); + write(cb); + } + + public void print(final int i) { + final CharBuffer cb = CharBuffer.wrap(Integer.toString(i)); + write(cb); + } + + public void print(final long l) { + final CharBuffer cb = CharBuffer.wrap(Long.toString(l)); + write(cb); + } + + public void print(final float f) { + final CharBuffer cb = CharBuffer.wrap(Float.toString(f)); + write(cb); + } + + public void print(final double d) { + final CharBuffer cb = CharBuffer.wrap(Double.toString(d)); + write(cb); + } + + public void print(final char[] s) { + final CharBuffer cb = CharBuffer.wrap(s); + write(cb); + } + + public void print(final String s) { + final CharBuffer cb = CharBuffer.wrap(s == null ? "null" : s); + write(cb); + } + + public void print(final Object obj) { + final CharBuffer cb = CharBuffer.wrap(obj == null ? "null" : obj.toString()); + write(cb); + } + + public void println() { + print('\n'); + } + + public void println(final boolean b) { + print(b); + print('\n'); + } + + public void println(final char c) { + print(c); + print('\n'); + } + + public void println(final int i) { + print(i); + print('\n'); + } + + public void println(final long l) { + print(l); + print('\n'); + } + + public void println(final float f) { + print(f); + print('\n'); + } + + public void println(final double d) { + print(d); + print('\n'); + } + + public void println(final char[] s) { + print(s); + print('\n'); + } + + public void println(final String s) { + print(s); + print('\n'); + } + + public void println(final Object obj) { + print(obj); + print('\n'); + } + + public void printf(final String format, final Object... args) { + print(String.format(format, args)); + } + + public void printf(final Locale l, final String format, final Object... args) { + print(String.format(l, format, args)); + } + + + public void format(final String format, final Object... args) { + printf(format, args); + } + + public void format(final Locale l, final String format, final Object... args) { + printf(l, format, args); + } + + public void append(final CharSequence csq) { + if (csq == null) + write("null"); + else + write(csq.toString()); + } + + public void append(final CharSequence csq, final int start, final int end) { + CharSequence cs = (csq == null ? "null" : csq); + write(cs.subSequence(start, end).toString()); + } + + public void append(final char c) { + write(c); + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriterDelegate.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriterDelegate.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletPrintWriterDelegate.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,251 @@ +package io.undertow.servlet.spec; + +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; + +import sun.reflect.ReflectionFactory; + +/** + * @author Stuart Douglas + */ +public final class ServletPrintWriterDelegate extends PrintWriter { + private ServletPrintWriterDelegate() { + super((OutputStream) null); + } + + private static final Constructor CONSTRUCTOR; + + static { + CONSTRUCTOR = AccessController.doPrivileged(new PrivilegedAction>() { + @Override + public Constructor run() { + try { + return (Constructor)ReflectionFactory.getReflectionFactory().newConstructorForSerialization(ServletPrintWriterDelegate.class, Object.class.getDeclaredConstructor()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + }); + } + + public static ServletPrintWriterDelegate newInstance(final ServletPrintWriter servletPrintWriter) { + final ServletPrintWriterDelegate delegate; + if (System.getSecurityManager() == null) { + try { + delegate = CONSTRUCTOR.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + delegate = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletPrintWriterDelegate run() { + try { + return CONSTRUCTOR.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + }); + } + delegate.setServletPrintWriter(servletPrintWriter); + return delegate; + } + + private ServletPrintWriter servletPrintWriter; + + public void setServletPrintWriter(final ServletPrintWriter servletPrintWriter) { + this.servletPrintWriter = servletPrintWriter; + } + + @Override + public void flush() { + servletPrintWriter.flush(); + } + + @Override + public void close() { + servletPrintWriter.close(); + } + + @Override + public boolean checkError() { + return servletPrintWriter.checkError(); + } + + @Override + public void write(final int c) { + servletPrintWriter.write(c); + } + + @Override + public void write(final char[] buf, final int off, final int len) { + servletPrintWriter.write(buf, off, len); + } + + @Override + public void write(final char[] buf) { + servletPrintWriter.write(buf); + } + + @Override + public void write(final String s, final int off, final int len) { + servletPrintWriter.write(s, off, len); + } + + @Override + public void write(final String s) { + servletPrintWriter.write(s); + } + + @Override + public void print(final boolean b) { + servletPrintWriter.print(b); + } + + @Override + public void print(final char c) { + servletPrintWriter.print(c); + } + + @Override + public void print(final int i) { + servletPrintWriter.print(i); + } + + @Override + public void print(final long l) { + servletPrintWriter.print(l); + } + + @Override + public void print(final float f) { + servletPrintWriter.print(f); + } + + @Override + public void print(final double d) { + servletPrintWriter.print(d); + } + + @Override + public void print(final char[] s) { + servletPrintWriter.print(s); + } + + @Override + public void print(final String s) { + servletPrintWriter.print(s); + } + + @Override + public void print(final Object obj) { + servletPrintWriter.print(obj); + } + + @Override + public void println() { + servletPrintWriter.println(); + } + + @Override + public void println(final boolean x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final char x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final int x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final long x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final float x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final double x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final char[] x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final String x) { + servletPrintWriter.println(x); + } + + @Override + public void println(final Object x) { + servletPrintWriter.println(x); + } + + @Override + public PrintWriter printf(final String format, final Object... args) { + servletPrintWriter.printf(format, args); + return this; + } + + @Override + public PrintWriter printf(final Locale l, final String format, final Object... args) { + servletPrintWriter.printf(l, format, args); + return this; + } + + @Override + public PrintWriter format(final String format, final Object... args) { + servletPrintWriter.format(format, args); + return this; + } + + @Override + public PrintWriter format(final Locale l, final String format, final Object... args) { + servletPrintWriter.format(l, format, args); + return this; + } + + @Override + public PrintWriter append(final CharSequence csq) { + servletPrintWriter.append(csq); + return this; + } + + @Override + public PrintWriter append(final CharSequence csq, final int start, final int end) { + servletPrintWriter.append(csq, start, end); + return this; + } + + @Override + public PrintWriter append(final char c) { + servletPrintWriter.append(c); + return this; + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletRegistrationImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletRegistrationImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/ServletRegistrationImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,203 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.HttpMethodConstraintElement; +import javax.servlet.MultipartConfigElement; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletSecurityElement; +import javax.servlet.annotation.ServletSecurity; + +import io.undertow.UndertowMessages; +import io.undertow.servlet.api.Deployment; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.HttpMethodSecurityInfo; +import io.undertow.servlet.api.SecurityConstraint; +import io.undertow.servlet.api.SecurityInfo; +import io.undertow.servlet.api.SecurityInfo.EmptyRoleSemantic; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.api.ServletSecurityInfo; +import io.undertow.servlet.api.TransportGuaranteeType; +import io.undertow.servlet.api.WebResourceCollection; + +import static javax.servlet.annotation.ServletSecurity.TransportGuarantee.CONFIDENTIAL; + +/** + * @author Stuart Douglas + */ +public class ServletRegistrationImpl implements ServletRegistration, ServletRegistration.Dynamic { + + private final ServletInfo servletInfo; + private final Deployment deployment; + + public ServletRegistrationImpl(final ServletInfo servletInfo, final Deployment deployment) { + this.servletInfo = servletInfo; + this.deployment = deployment; + } + + @Override + public void setLoadOnStartup(final int loadOnStartup) { + servletInfo.setLoadOnStartup(loadOnStartup); + } + + @Override + public Set setServletSecurity(final ServletSecurityElement constraint) { + if (constraint == null) { + throw UndertowMessages.MESSAGES.argumentCannotBeNull("constraint"); + } + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + + //this is not super efficient, but it does not really matter + final Set urlPatterns = new HashSet(); + for (SecurityConstraint sc : deploymentInfo.getSecurityConstraints()) { + for (WebResourceCollection webResources : sc.getWebResourceCollections()) { + urlPatterns.addAll(webResources.getUrlPatterns()); + } + } + final Set ret = new HashSet(); + for (String url : servletInfo.getMappings()) { + if (urlPatterns.contains(url)) { + ret.add(url); + } + } + ServletSecurityInfo info = new ServletSecurityInfo(); + servletInfo.setServletSecurityInfo(info); + info.setTransportGuaranteeType(constraint.getTransportGuarantee() == CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) + .setEmptyRoleSemantic(emptyRoleSemantic(constraint.getEmptyRoleSemantic())) + .addRolesAllowed(constraint.getRolesAllowed()); + + for (final HttpMethodConstraintElement methodConstraint : constraint.getHttpMethodConstraints()) { + info.addHttpMethodSecurityInfo(new HttpMethodSecurityInfo() + .setTransportGuaranteeType(methodConstraint.getTransportGuarantee() == CONFIDENTIAL ? TransportGuaranteeType.CONFIDENTIAL : TransportGuaranteeType.NONE) + .setMethod(methodConstraint.getMethodName()) + .setEmptyRoleSemantic(emptyRoleSemantic(methodConstraint.getEmptyRoleSemantic())) + .addRolesAllowed(methodConstraint.getRolesAllowed())); + } + return ret; + } + + private SecurityInfo.EmptyRoleSemantic emptyRoleSemantic(final ServletSecurity.EmptyRoleSemantic emptyRoleSemantic) { + switch (emptyRoleSemantic) { + case PERMIT: + return EmptyRoleSemantic.PERMIT; + case DENY: + return EmptyRoleSemantic.DENY; + default: + return null; + } + } + + @Override + public void setMultipartConfig(final MultipartConfigElement multipartConfig) { + servletInfo.setMultipartConfig(multipartConfig); + } + + @Override + public void setRunAsRole(final String roleName) { + servletInfo.setRunAs(roleName); + } + + @Override + public void setAsyncSupported(final boolean isAsyncSupported) { + servletInfo.setAsyncSupported(isAsyncSupported); + } + + @Override + public Set addMapping(final String... urlPatterns) { + DeploymentInfo deploymentInfo = deployment.getDeploymentInfo(); + final Set ret = new HashSet(); + final Set existing = new HashSet(); + for (ServletInfo s : deploymentInfo.getServlets().values()) { + if (!s.getName().equals(servletInfo.getName())) { + existing.addAll(s.getMappings()); + } + } + for (String pattern : urlPatterns) { + if (existing.contains(pattern)) { + ret.add(pattern); + } + } + //only update if no changes have been made + if (ret.isEmpty()) { + for (String pattern : urlPatterns) { + if (!servletInfo.getMappings().contains(pattern)) { + servletInfo.addMapping(pattern); + } + } + } + deployment.getServletPaths().invalidate(); + return ret; + } + + @Override + public Collection getMappings() { + return servletInfo.getMappings(); + } + + @Override + public String getRunAsRole() { + return servletInfo.getRunAs(); + } + + @Override + public String getName() { + return servletInfo.getName(); + } + + @Override + public String getClassName() { + return servletInfo.getServletClass().getName(); + } + + + @Override + public boolean setInitParameter(final String name, final String value) { + if (servletInfo.getInitParams().containsKey(name)) { + return false; + } + servletInfo.addInitParam(name, value); + return true; + } + + @Override + public String getInitParameter(final String name) { + return servletInfo.getInitParams().get(name); + } + + @Override + public Set setInitParameters(final Map initParameters) { + final Set ret = new HashSet(); + for (Map.Entry entry : initParameters.entrySet()) { + if (!setInitParameter(entry.getKey(), entry.getValue())) { + ret.add(entry.getKey()); + } + } + return ret; + } + + @Override + public Map getInitParameters() { + return servletInfo.getInitParams(); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/SessionCookieConfigImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/SessionCookieConfigImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/SessionCookieConfigImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,165 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.spec; + +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.SessionConfig; +import io.undertow.servlet.UndertowServletMessages; + +import javax.servlet.SessionCookieConfig; + +/** + * @author Stuart Douglas + */ +public class SessionCookieConfigImpl implements SessionCookieConfig, SessionConfig { + + private final ServletContextImpl servletContext; + private final io.undertow.server.session.SessionCookieConfig delegate; + private SessionConfig fallback; + + public SessionCookieConfigImpl(final ServletContextImpl servletContext) { + this.servletContext = servletContext; + this.delegate = new io.undertow.server.session.SessionCookieConfig(); + } + + @Override + public String rewriteUrl(final String originalUrl, final String sessionid) { + return originalUrl; + } + + @Override + public void setSessionId(final HttpServerExchange exchange, final String sessionId) { + delegate.setSessionId(exchange, sessionId); + } + + @Override + public void clearSession(final HttpServerExchange exchange, final String sessionId) { + delegate.clearSession(exchange, sessionId); + } + + @Override + public String findSessionId(final HttpServerExchange exchange) { + String existing = delegate.findSessionId(exchange); + if(existing != null) { + return existing; + } + if(fallback != null) { + return fallback.findSessionId(exchange); + } + return null; + } + + @Override + public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) { + String existing = delegate.findSessionId(exchange); + if (existing != null) { + return SessionCookieSource.COOKIE; + } + if(fallback != null) { + String id = fallback.findSessionId(exchange); + return id != null ? fallback.sessionCookieSource(exchange) : SessionCookieSource.NONE; + } + return SessionCookieSource.NONE; + } + + public String getName() { + return delegate.getCookieName(); + } + + public void setName(final String name) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setCookieName(name); + } + + public String getDomain() { + return delegate.getDomain(); + } + + public void setDomain(final String domain) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setDomain(domain); + } + + public String getPath() { + return delegate.getPath(); + } + + public void setPath(final String path) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setPath(path); + } + + public String getComment() { + return delegate.getComment(); + } + + public void setComment(final String comment) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setComment(comment); + } + + public boolean isHttpOnly() { + return delegate.isHttpOnly(); + } + + public void setHttpOnly(final boolean httpOnly) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setHttpOnly(httpOnly); + } + + public boolean isSecure() { + return delegate.isSecure(); + } + + public void setSecure(final boolean secure) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + delegate.setSecure(secure); + } + + public int getMaxAge() { + return delegate.getMaxAge(); + } + + public void setMaxAge(final int maxAge) { + if(servletContext.isInitialized()) { + throw UndertowServletMessages.MESSAGES.servletContextAlreadyInitialized(); + } + this.delegate.setMaxAge(maxAge); + } + + public SessionConfig getFallback() { + return fallback; + } + + public void setFallback(final SessionConfig fallback) { + this.fallback = fallback; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletInputStream.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletInputStream.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletInputStream.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,260 @@ +package io.undertow.servlet.spec; + +import io.undertow.servlet.UndertowServletMessages; +import org.xnio.Buffers; +import org.xnio.ChannelListener; +import org.xnio.IoUtils; +import org.xnio.Pool; +import org.xnio.Pooled; +import org.xnio.channels.Channels; +import org.xnio.channels.StreamSourceChannel; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; + +import static org.xnio.Bits.allAreClear; +import static org.xnio.Bits.anyAreClear; +import static org.xnio.Bits.anyAreSet; + +/** + * Servlet input stream implementation. This stream is non-buffered, and is only used for + * upgraded requests + * + * @author Stuart Douglas + */ +public class UpgradeServletInputStream extends ServletInputStream { + + private final StreamSourceChannel channel; + private final Pool bufferPool; + private final Executor ioExecutor; + + private volatile ReadListener listener; + + /** + * If this stream is ready for a read + */ + private static final int FLAG_READY = 1; + private static final int FLAG_CLOSED = 1 << 1; + private static final int FLAG_FINISHED = 1 << 2; + private static final int FLAG_ON_DATA_READ_CALLED = 1 << 3; + + private int state; + private Pooled pooled; + + public UpgradeServletInputStream(final StreamSourceChannel channel, final Pool bufferPool, Executor ioExecutor) { + this.channel = channel; + this.bufferPool = bufferPool; + this.ioExecutor = ioExecutor; + } + + @Override + public boolean isFinished() { + return anyAreSet(state, FLAG_FINISHED); + } + + @Override + public boolean isReady() { + return anyAreSet(state, FLAG_READY) && !isFinished(); + } + + @Override + public void setReadListener(final ReadListener readListener) { + if (readListener == null) { + throw UndertowServletMessages.MESSAGES.listenerCannotBeNull(); + } + if (listener != null) { + throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); + } + + listener = readListener; + channel.getReadSetter().set(new ServletInputStreamChannelListener()); + + //we resume from an async task, after the request has been dispatched + ioExecutor.execute(new Runnable() { + @Override + public void run() { + channel.wakeupReads(); + } + }); + } + + @Override + public int read() throws IOException { + byte[] b = new byte[1]; + int read = read(b); + if (read == -1) { + return -1; + } + return b[0] & 0xff; + } + + @Override + public int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + if (listener != null) { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + } else { + readIntoBuffer(); + } + if (anyAreSet(state, FLAG_FINISHED)) { + return -1; + } + if (len == 0) { + return 0; + } + ByteBuffer buffer = pooled.getResource(); + int copied = Buffers.copy(ByteBuffer.wrap(b, off, len), buffer); + if (!buffer.hasRemaining()) { + pooled.free(); + pooled = null; + if (listener != null) { + readIntoBufferNonBlocking(); + } + } + return copied; + } + + private void readIntoBuffer() throws IOException { + if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { + pooled = bufferPool.allocate(); + + int res = Channels.readBlocking(channel, pooled.getResource()); + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } + } + } + + private void readIntoBufferNonBlocking() throws IOException { + if (pooled == null && !anyAreSet(state, FLAG_FINISHED)) { + pooled = bufferPool.allocate(); + if (listener == null) { + int res = channel.read(pooled.getResource()); + if (res == 0) { + pooled.free(); + pooled = null; + return; + } + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } + } else { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + int res = channel.read(pooled.getResource()); + pooled.getResource().flip(); + if (res == -1) { + state |= FLAG_FINISHED; + pooled.free(); + pooled = null; + } else if (res == 0) { + state &= ~FLAG_READY; + pooled.free(); + pooled = null; + if(Thread.currentThread() == channel.getIoThread()) { + channel.resumeReads(); + } else { + ioExecutor.execute(new Runnable() { + @Override + public void run() { + channel.resumeReads(); + } + }); + } + } + } + } + } + + @Override + public int available() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + readIntoBufferNonBlocking(); + if (anyAreSet(state, FLAG_FINISHED)) { + return 0; + } + if (pooled == null) { + return 0; + } + return pooled.getResource().remaining(); + } + + @Override + public void close() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + return; + } + while (allAreClear(state, FLAG_FINISHED)) { + readIntoBuffer(); + if (pooled != null) { + pooled.free(); + pooled = null; + } + } + if (pooled != null) { + pooled.free(); + pooled = null; + } + channel.shutdownReads(); + state |= FLAG_FINISHED | FLAG_CLOSED; + } + + private class ServletInputStreamChannelListener implements ChannelListener { + @Override + public void handleEvent(final StreamSourceChannel channel) { + if (anyAreSet(state, FLAG_FINISHED)) { + return; + } + state |= FLAG_READY; + try { + readIntoBufferNonBlocking(); + if (pooled != null) { + state |= FLAG_READY; + if (!anyAreSet(state, FLAG_FINISHED)) { + listener.onDataAvailable(); + } + } + } catch (Exception e) { + listener.onError(e); + IoUtils.safeClose(channel); + } + if (anyAreSet(state, FLAG_FINISHED)) { + if (anyAreClear(state, FLAG_ON_DATA_READ_CALLED)) { + try { + state |= FLAG_ON_DATA_READ_CALLED; + channel.shutdownReads(); + listener.onAllDataRead(); + } catch (IOException e) { + listener.onError(e); + IoUtils.safeClose(channel); + } + } + } else if(isReady()) { + channel.suspendReads(); + } + } + + + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletOutputStream.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletOutputStream.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/UpgradeServletOutputStream.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,236 @@ +package io.undertow.servlet.spec; + +import io.undertow.servlet.UndertowServletMessages; +import org.xnio.ChannelListener; +import org.xnio.IoUtils; +import org.xnio.channels.Channels; +import org.xnio.channels.StreamSinkChannel; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; + +import static org.xnio.Bits.anyAreClear; +import static org.xnio.Bits.anyAreSet; + +/** + * Output stream used for upgraded requests. This is different to {@link ServletOutputStreamImpl} + * as it does no buffering, and it not tied to an exchange. + * + * @author Stuart Douglas + */ +public class UpgradeServletOutputStream extends ServletOutputStream { + + private final StreamSinkChannel channel; + + private WriteListener listener; + private final Executor ioExecutor; + + /** + * If this stream is ready for a write + */ + private static final int FLAG_READY = 1; + private static final int FLAG_CLOSED = 1 << 1; + private static final int FLAG_DELEGATE_SHUTDOWN = 1 << 2; + + private int state; + + /** + * The buffer that is in the process of being written out + */ + private ByteBuffer buffer; + + protected UpgradeServletOutputStream(final StreamSinkChannel channel, Executor ioExecutor) { + this.channel = channel; + this.ioExecutor = ioExecutor; + } + + @Override + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + if (listener == null) { + Channels.writeBlocking(channel, ByteBuffer.wrap(b, off, len)); + } else { + if (anyAreClear(state, FLAG_READY)) { + throw UndertowServletMessages.MESSAGES.streamNotReady(); + } + int res; + ByteBuffer buffer = ByteBuffer.wrap(b); + do { + res = channel.write(buffer); + if (res == 0) { + + ByteBuffer copy = ByteBuffer.allocate(buffer.remaining()); + copy.put(buffer); + copy.flip(); + this.buffer = copy; + state = state & ~FLAG_READY; + if (Thread.currentThread() == channel.getIoThread()) { + channel.resumeWrites(); + } else { + ioExecutor.execute(new Runnable() { + @Override + public void run() { + channel.resumeWrites(); + } + }); + } + return; + } + } while (buffer.hasRemaining()); + } + } + + @Override + public void write(final int b) throws IOException { + write(new byte[]{(byte) b}, 0, 1); + } + + @Override + public void flush() throws IOException { + if (anyAreSet(state, FLAG_CLOSED)) { + throw UndertowServletMessages.MESSAGES.streamIsClosed(); + } + if (listener == null) { + Channels.flushBlocking(channel); + } + } + + @Override + public void close() throws IOException { + state |= FLAG_CLOSED; + state &= ~FLAG_READY; + if (listener == null) { + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + Channels.flushBlocking(channel); + } else { + if (buffer == null) { + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + if (!channel.flush()) { + if (Thread.currentThread() == channel.getIoThread()) { + channel.resumeWrites(); + } else { + ioExecutor.execute(new Runnable() { + @Override + public void run() { + channel.resumeWrites(); + } + }); + } + } + } + } + } + + void closeBlocking() throws IOException { + state |= FLAG_CLOSED; + try { + if (buffer != null) { + Channels.writeBlocking(channel, buffer); + } + channel.shutdownWrites(); + Channels.flushBlocking(channel); + } finally { + channel.close(); + } + } + + @Override + public boolean isReady() { + if (listener == null) { + //TODO: is this the correct behaviour? + throw UndertowServletMessages.MESSAGES.streamNotInAsyncMode(); + } + return anyAreSet(state, FLAG_READY); + } + + @Override + public void setWriteListener(final WriteListener writeListener) { + if (writeListener == null) { + throw UndertowServletMessages.MESSAGES.paramCannotBeNull("writeListener"); + } + if (listener != null) { + throw UndertowServletMessages.MESSAGES.listenerAlreadySet(); + } + listener = writeListener; + channel.getWriteSetter().set(new WriteChannelListener()); + state |= FLAG_READY; + ioExecutor.execute(new Runnable() { + @Override + public void run() { + channel.resumeWrites(); + } + }); + } + + private class WriteChannelListener implements ChannelListener { + + @Override + public void handleEvent(final StreamSinkChannel channel) { + //flush the channel if it is closed + if (anyAreSet(state, FLAG_DELEGATE_SHUTDOWN)) { + try { + //either it will work, and the channel is closed + //or it won't, and we continue with writes resumed + channel.flush(); + return; + } catch (IOException e) { + handleError(channel, e); + } + } + //if there is data still to write + if (buffer != null) { + int res; + do { + try { + res = channel.write(buffer); + if (res == 0) { + return; + } + } catch (IOException e) { + handleError(channel, e); + } + } while (buffer.hasRemaining()); + buffer = null; + } + if (anyAreSet(state, FLAG_CLOSED)) { + try { + channel.shutdownWrites(); + state |= FLAG_DELEGATE_SHUTDOWN; + channel.flush(); //if this does not succeed we are already resumed anyway + } catch (IOException e) { + handleError(channel, e); + } + + } else { + state |= FLAG_READY; + channel.suspendWrites(); + try { + listener.onWritePossible(); + } catch (IOException e) { + listener.onError(e); + } + } + } + + private void handleError(final StreamSinkChannel channel, final IOException e) { + try { + listener.onError(e); + } finally { + IoUtils.safeClose(channel); + } + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/spec/WebConnectionImpl.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/spec/WebConnectionImpl.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/spec/WebConnectionImpl.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,43 @@ +package io.undertow.servlet.spec; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; + +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.WebConnection; + +import org.xnio.Pool; +import org.xnio.StreamConnection; + +/** + * @author Stuart Douglas + */ +public class WebConnectionImpl implements WebConnection { + + private final UpgradeServletOutputStream outputStream; + private final UpgradeServletInputStream inputStream; + private final Executor ioExecutor; + + public WebConnectionImpl(final StreamConnection channel, Pool bufferPool, Executor ioExecutor) { + this.ioExecutor = ioExecutor; + this.outputStream = new UpgradeServletOutputStream(channel.getSinkChannel(), ioExecutor); + this.inputStream = new UpgradeServletInputStream(channel.getSourceChannel(), bufferPool, ioExecutor); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return inputStream; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return outputStream; + } + + @Override + public void close() throws Exception { + outputStream.closeBlocking(); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/ConstructorInstanceFactory.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/ConstructorInstanceFactory.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/ConstructorInstanceFactory.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,54 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; + +/** + * @author Stuart Douglas + */ +public class ConstructorInstanceFactory implements InstanceFactory { + + private final Constructor constructor; + + public ConstructorInstanceFactory(final Constructor constructor) { + constructor.setAccessible(true); + this.constructor = constructor; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + try { + final T instance = constructor.newInstance(); + return new ImmediateInstanceHandle(instance); + } catch (IllegalAccessException e) { + InstantiationException ite = new InstantiationException(); + ite.initCause(e); + throw ite; + } catch (InvocationTargetException e) { + InstantiationException ite = new InstantiationException(); + ite.initCause(e); + throw ite; + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/DefaultClassIntrospector.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/DefaultClassIntrospector.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/DefaultClassIntrospector.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,38 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import io.undertow.servlet.api.ClassIntrospecter; +import io.undertow.servlet.api.InstanceFactory; + +/** + * @author Stuart Douglas + */ +public class DefaultClassIntrospector implements ClassIntrospecter { + + public static final DefaultClassIntrospector INSTANCE = new DefaultClassIntrospector(); + + private DefaultClassIntrospector() { + } + + @Override + public InstanceFactory createInstanceFactory(final Class clazz) throws NoSuchMethodException { + return new ConstructorInstanceFactory(clazz.getDeclaredConstructor()); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/EmptyEnumeration.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/EmptyEnumeration.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/EmptyEnumeration.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,48 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import java.util.Enumeration; + +/** + * @author Stuart Douglas + */ +public class EmptyEnumeration implements Enumeration { + + private static final Enumeration INSTANCE = new EmptyEnumeration(); + + @SuppressWarnings("unchecked") + public static Enumeration instance() { + return (Enumeration) INSTANCE; + } + + private EmptyEnumeration() { + + } + + @Override + public boolean hasMoreElements() { + return false; + } + + @Override + public Object nextElement() { + return null; + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceFactory.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceFactory.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceFactory.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,39 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; + +/** + * @author Stuart Douglas + */ +public class ImmediateInstanceFactory implements InstanceFactory { + + private final T instance; + + public ImmediateInstanceFactory(final T instance) { + this.instance = instance; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + return new ImmediateInstanceHandle(instance); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceHandle.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceHandle.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/ImmediateInstanceHandle.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import io.undertow.servlet.api.InstanceHandle; + +/** + * @author Stuart Douglas + */ +public class ImmediateInstanceHandle implements InstanceHandle { + + private final T instance; + + public ImmediateInstanceHandle(final T instance) { + this.instance = instance; + } + + @Override + public T getInstance() { + return instance; + } + + @Override + public void release() { + + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/InMemorySessionPersistence.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/InMemorySessionPersistence.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/InMemorySessionPersistence.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,89 @@ +package io.undertow.servlet.util; + +import io.undertow.servlet.UndertowServletLogger; +import io.undertow.servlet.api.SessionPersistenceManager; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Session persistence implementation that simply stores session information in memory. + * + * @author Stuart Douglas + */ +public class InMemorySessionPersistence implements SessionPersistenceManager { + + private static final Map> data = new ConcurrentHashMap>(); + + @Override + public void persistSessions(String deploymentName, Map sessionData) { + try { + final Map serializedData = new HashMap(); + for (Map.Entry sessionEntry : sessionData.entrySet()) { + Map data = new HashMap(); + for (Map.Entry sessionAttribute : sessionEntry.getValue().getSessionData().entrySet()) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final ObjectOutputStream objectOutputStream = new ObjectOutputStream(out); + objectOutputStream.writeObject(sessionAttribute.getValue()); + objectOutputStream.close(); + data.put(sessionAttribute.getKey(), out.toByteArray()); + } catch (Exception e) { + UndertowServletLogger.ROOT_LOGGER.failedToPersistSessionAttribute(sessionAttribute.getKey(), sessionAttribute.getValue(), sessionEntry.getKey(), e); + } + } + serializedData.put(sessionEntry.getKey(), new SessionEntry(sessionEntry.getValue().getExpiration(), data)); + } + data.put(deploymentName, serializedData); + } catch (Exception e) { + UndertowServletLogger.ROOT_LOGGER.failedToPersistSessions(e); + } + + } + + @Override + public Map loadSessionAttributes(String deploymentName, final ClassLoader classLoader) { + try { + long time = System.currentTimeMillis(); + Map data = this.data.remove(deploymentName); + if (data != null) { + Map ret = new HashMap(); + for (Map.Entry sessionEntry : data.entrySet()) { + if (sessionEntry.getValue().expiry.getTime() > time) { + Map session = new HashMap(); + for (Map.Entry sessionAttribute : sessionEntry.getValue().data.entrySet()) { + final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(sessionAttribute.getValue())); + session.put(sessionAttribute.getKey(), in.readObject()); + } + ret.put(sessionEntry.getKey(), new PersistentSession(sessionEntry.getValue().expiry, session)); + } + } + return ret; + } + } catch (Exception e) { + UndertowServletLogger.ROOT_LOGGER.failedtoLoadPersistentSessions(e); + } + return null; + } + + @Override + public void clear(String deploymentName) { + } + + static final class SessionEntry { + private final Date expiry; + private final Map data; + + private SessionEntry(Date expiry, Map data) { + this.expiry = expiry; + this.data = data; + } + + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/IteratorEnumeration.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/IteratorEnumeration.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/IteratorEnumeration.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,46 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.util; + +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Wrapper to convert an iterator to an enumeration + * + * @author Stuart Douglas + */ +public class IteratorEnumeration implements Enumeration { + + private final Iterator iterator; + + public IteratorEnumeration(final Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public T nextElement() { + return iterator.next(); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/util/SavedRequest.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/util/SavedRequest.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/util/SavedRequest.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,130 @@ +package io.undertow.servlet.util; + +import io.undertow.UndertowLogger; +import io.undertow.UndertowOptions; +import io.undertow.server.Connectors; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.session.Session; +import io.undertow.servlet.handlers.ServletRequestContext; +import io.undertow.servlet.spec.HttpSessionImpl; +import io.undertow.util.HeaderMap; +import io.undertow.util.HeaderValues; +import io.undertow.util.Headers; +import io.undertow.util.HttpString; +import io.undertow.util.ImmediatePooled; + +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.util.Iterator; + +/** + * Saved servlet request. + * + * @author Stuart Douglas + */ +public class SavedRequest implements Serializable { + + private static final String SESSION_KEY = SavedRequest.class.getName(); + + private final byte[] data; + private final int dataLength; + private final HttpString method; + private final String requestUri; + private final HeaderMap headerMap; + + public SavedRequest(byte[] data, int dataLength, HttpString method, String requestUri, HeaderMap headerMap) { + this.data = data; + this.dataLength = dataLength; + this.method = method; + this.requestUri = requestUri; + this.headerMap = headerMap; + } + + public static void trySaveRequest(final HttpServerExchange exchange) { + int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, 16384); + if (maxSize > 0) { + //if this request has a body try and cache the response + if (!exchange.isRequestComplete()) { + final long requestContentLength = exchange.getRequestContentLength(); + if (requestContentLength > maxSize) { + UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); + return;//failed to save the request, we just return + } + //TODO: we should really be used pooled buffers + //TODO: we should probably limit the number of saved requests at any given time + byte[] buffer = new byte[maxSize]; + int read = 0; + int res = 0; + InputStream in = exchange.getInputStream(); + try { + while ((res = in.read(buffer, read, buffer.length - read)) > 0) { + read += res; + if (read == maxSize) { + UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI()); + return;//failed to save the request, we just return + } + } + HeaderMap headers = new HeaderMap(); + for(HeaderValues entry : exchange.getRequestHeaders()) { + if(entry.getHeaderName().equals(Headers.CONTENT_LENGTH) || + entry.getHeaderName().equals(Headers.TRANSFER_ENCODING) || + entry.getHeaderName().equals(Headers.CONNECTION)) { + continue; + } + headers.putAll(entry.getHeaderName(), entry); + } + SavedRequest request = new SavedRequest(buffer, read, exchange.getRequestMethod(), exchange.getRequestURI(), exchange.getRequestHeaders()); + final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true); + Session underlyingSession; + if(System.getSecurityManager() == null) { + underlyingSession = session.getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + underlyingSession.setAttribute(SESSION_KEY, request); + } catch (IOException e) { + UndertowLogger.REQUEST_IO_LOGGER.ioException(e); + } + } + } + } + + public static void tryRestoreRequest(final HttpServerExchange exchange, HttpSession session) { + if(session instanceof HttpSessionImpl) { + + Session underlyingSession; + if(System.getSecurityManager() == null) { + underlyingSession = ((HttpSessionImpl) session).getSession(); + } else { + underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session)); + } + SavedRequest request = (SavedRequest) underlyingSession.getAttribute(SESSION_KEY); + if(request != null) { + if(request.requestUri.equals(exchange.getRequestURI()) && exchange.isRequestComplete()) { + UndertowLogger.REQUEST_LOGGER.debugf("restoring request body for request to %s", request.requestUri); + exchange.setRequestMethod(request.method); + Connectors.ungetRequestBytes(exchange, new ImmediatePooled(ByteBuffer.wrap(request.data, 0, request.dataLength))); + underlyingSession.removeAttribute(SESSION_KEY); + //clear the existing header map of everything except the connection header + //TODO: are there other headers we should preserve? + Iterator headerIterator = exchange.getRequestHeaders().iterator(); + while (headerIterator.hasNext()) { + HeaderValues header = headerIterator.next(); + if(!header.getHeaderName().equals(Headers.CONNECTION)) { + headerIterator.remove(); + } + } + for(HeaderValues header : request.headerMap) { + exchange.getRequestHeaders().putAll(header.getHeaderName(), header); + } + } + } + } + } + +} Index: 3rdParty_sources/undertow/io/undertow/servlet/websockets/SecurityActions.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/websockets/SecurityActions.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/websockets/SecurityActions.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,38 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2014 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.websockets; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import io.undertow.servlet.handlers.ServletRequestContext; + +class SecurityActions { + static ServletRequestContext requireCurrentServletRequestContext() { + if (System.getSecurityManager() == null) { + return ServletRequestContext.requireCurrent(); + } else { + return AccessController.doPrivileged(new PrivilegedAction() { + @Override + public ServletRequestContext run() { + return ServletRequestContext.requireCurrent(); + } + }); + } + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/websockets/ServletWebSocketHttpExchange.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/websockets/ServletWebSocketHttpExchange.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/websockets/ServletWebSocketHttpExchange.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,221 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed 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 io.undertow.servlet.websockets; + +import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpUpgradeListener; +import io.undertow.util.AttachmentKey; +import io.undertow.websockets.spi.WebSocketHttpExchange; +import org.xnio.FinishedIoFuture; +import org.xnio.FutureResult; +import org.xnio.IoFuture; +import org.xnio.IoUtils; +import org.xnio.Pool; + +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** +* @author Stuart Douglas +*/ +public class ServletWebSocketHttpExchange implements WebSocketHttpExchange { + + private final HttpServletRequest request; + private final HttpServletResponse response; + private final HttpServerExchange exchange; + + public ServletWebSocketHttpExchange(final HttpServletRequest request, final HttpServletResponse response) { + this.request = request; + this.response = response; + this.exchange = SecurityActions.requireCurrentServletRequestContext().getOriginalRequest().getExchange(); + } + + + @Override + public void putAttachment(final AttachmentKey key, final T value) { + exchange.putAttachment(key, value); + } + + @Override + public T getAttachment(final AttachmentKey key) { + return exchange.getAttachment(key); + } + + @Override + public String getRequestHeader(final String headerName) { + return request.getHeader(headerName); + } + + @Override + public Map> getRequestHeaders() { + Map> headers = new HashMap>(); + final Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + final Enumeration theHeaders = request.getHeaders(header); + final List vals = new ArrayList(); + headers.put(header, vals); + while (theHeaders.hasMoreElements()) { + vals.add(theHeaders.nextElement()); + } + + } + return Collections.unmodifiableMap(headers); + } + + @Override + public String getResponseHeader(final String headerName) { + return response.getHeader(headerName); + } + + @Override + public Map> getResponseHeaders() { + Map> headers = new HashMap>(); + final Collection headerNames = response.getHeaderNames(); + for (String header : headerNames) { + headers.put(header, new ArrayList(response.getHeaders(header))); + } + return Collections.unmodifiableMap(headers); + } + + @Override + public void setResponseHeaders(final Map> headers) { + for (String header : response.getHeaderNames()) { + response.setHeader(header, null); + } + + for (Map.Entry> entry : headers.entrySet()) { + for (String val : entry.getValue()) { + response.addHeader(entry.getKey(), val); + } + } + } + + @Override + public void setResponseHeader(final String headerName, final String headerValue) { + response.setHeader(headerName, headerValue); + } + + @Override + public void upgradeChannel(final HttpUpgradeListener upgradeCallback) { + exchange.upgradeChannel(upgradeCallback); + } + + @Override + public IoFuture sendData(final ByteBuffer data) { + try { + final ServletOutputStream outputStream = response.getOutputStream(); + while (data.hasRemaining()) { + outputStream.write(data.get()); + } + return new FinishedIoFuture(null); + } catch (IOException e) { + final FutureResult ioFuture = new FutureResult(); + ioFuture.setException(e); + return ioFuture.getIoFuture(); + } + } + + @Override + public IoFuture readRequestData() { + final ByteArrayOutputStream data = new ByteArrayOutputStream(); + try { + final ServletInputStream in = request.getInputStream(); + byte[] buf = new byte[1024]; + int r; + while ((r = in.read(buf)) != -1) { + data.write(buf, 0, r); + } + return new FinishedIoFuture(data.toByteArray()); + } catch (IOException e) { + final FutureResult ioFuture = new FutureResult(); + ioFuture.setException(e); + return ioFuture.getIoFuture(); + } + } + + + @Override + public void endExchange() { + //noop + } + + @Override + public void close() { + IoUtils.safeClose(exchange.getConnection()); + } + + @Override + public String getRequestScheme() { + return request.getScheme(); + } + + @Override + public String getRequestURI() { + return request.getRequestURI() + (request.getQueryString() == null ? "" : "?" + request.getQueryString()); + } + + @Override + public Pool getBufferPool() { + return exchange.getConnection().getBufferPool(); + } + + @Override + public String getQueryString() { + return request.getQueryString(); + } + + @Override + public Object getSession() { + return request.getSession(false); + } + + @Override + public Map> getRequestParameters() { + Map> params = new HashMap>(); + for(Map.Entry param : request.getParameterMap().entrySet()) { + params.put(param.getKey(), new ArrayList(Arrays.asList(param.getValue()))); + } + return params; + } + + @Override + public Principal getUserPrincipal() { + return request.getUserPrincipal(); + } + + @Override + public boolean isUserInRole(String role) { + return request.isUserInRole(role); + } +} Index: 3rdParty_sources/undertow/io/undertow/servlet/websockets/WebSocketServlet.java =================================================================== diff -u --- 3rdParty_sources/undertow/io/undertow/servlet/websockets/WebSocketServlet.java (revision 0) +++ 3rdParty_sources/undertow/io/undertow/servlet/websockets/WebSocketServlet.java (revision 2f8cfd72f6bf5004f3db48ba303c1b350bd85724) @@ -0,0 +1,105 @@ +package io.undertow.servlet.websockets; + +import io.undertow.UndertowLogger; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.HttpUpgradeListener; +import io.undertow.servlet.UndertowServletMessages; +import io.undertow.websockets.WebSocketConnectionCallback; +import io.undertow.websockets.core.WebSocketChannel; +import io.undertow.websockets.core.protocol.Handshake; +import io.undertow.websockets.core.protocol.version07.Hybi07Handshake; +import io.undertow.websockets.core.protocol.version08.Hybi08Handshake; +import io.undertow.websockets.core.protocol.version13.Hybi13Handshake; +import org.xnio.StreamConnection; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Stuart Douglas + */ +public class WebSocketServlet extends HttpServlet { + + public static final String SESSION_HANDLER = "io.undertow.handler"; + + private final List handshakes; + + private WebSocketConnectionCallback callback; + + public WebSocketServlet() { + this.handshakes = handshakes(); + } + + public WebSocketServlet(WebSocketConnectionCallback callback) { + this.callback = callback; + this.handshakes = handshakes(); + } + + + @Override + public void init(final ServletConfig config) throws ServletException { + super.init(config); + try { + final String sessionHandler = config.getInitParameter(SESSION_HANDLER); + if (sessionHandler != null) { + final Class clazz = Class.forName(sessionHandler, true, Thread.currentThread().getContextClassLoader()); + final Object handler = clazz.newInstance(); + this.callback = (WebSocketConnectionCallback) handler; + } + //TODO: set properties based on init params + + } catch (ClassNotFoundException e) { + throw new ServletException(e); + } catch (InstantiationException e) { + throw new ServletException(e); + } catch (IllegalAccessException e) { + throw new ServletException(e); + } + if (callback == null) { + throw UndertowServletMessages.MESSAGES.noWebSocketHandler(); + } + } + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + + final ServletWebSocketHttpExchange facade = new ServletWebSocketHttpExchange(req, resp); + Handshake handshaker = null; + for (Handshake method : handshakes) { + if (method.matches(facade)) { + handshaker = method; + break; + } + } + + if (handshaker == null) { + UndertowLogger.REQUEST_LOGGER.debug("Could not find hand shaker for web socket request"); + resp.sendError(400); + return; + } + final Handshake selected = handshaker; + facade.upgradeChannel(new HttpUpgradeListener() { + @Override + public void handleUpgrade(StreamConnection streamConnection, HttpServerExchange exchange) { + WebSocketChannel channel = selected.createChannel(facade, streamConnection, facade.getBufferPool()); + callback.onConnect(facade, channel); + } + }); + handshaker.handshake(facade); + } + + protected List handshakes() { + List handshakes = new ArrayList(); + handshakes.add(new Hybi13Handshake()); + handshakes.add(new Hybi08Handshake()); + handshakes.add(new Hybi07Handshake()); + return handshakes; + } + +}