Java tutorial
/* * Copyright 2008-2017 Hippo B.V. (http://www.onehippo.com) * * 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 org.hippoecm.frontend; import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.observation.EventListener; import javax.jcr.observation.EventListenerIterator; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.wicket.Component; import org.apache.wicket.DefaultPageManagerProvider; import org.apache.wicket.IPageRendererProvider; import org.apache.wicket.Page; import org.apache.wicket.RuntimeConfigurationType; import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.application.IClassResolver; import org.apache.wicket.core.request.handler.PageProvider; import org.apache.wicket.core.request.handler.RenderPageRequestHandler; import org.apache.wicket.core.request.handler.RenderPageRequestHandler.RedirectPolicy; import org.apache.wicket.core.util.resource.locator.IResourceNameIterator; import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator; import org.apache.wicket.page.IPageManagerContext; import org.apache.wicket.pageStore.IDataStore; import org.apache.wicket.pageStore.IPageStore; import org.apache.wicket.protocol.http.BufferedWebResponse; import org.apache.wicket.protocol.http.servlet.ServletWebRequest; import org.apache.wicket.protocol.http.servlet.ServletWebResponse; import org.apache.wicket.request.IRequestCycle; import org.apache.wicket.request.IRequestHandler; import org.apache.wicket.request.IRequestParameters; import org.apache.wicket.request.Request; import org.apache.wicket.request.Response; import org.apache.wicket.request.Url; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.cycle.IRequestCycleListener; import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.request.cycle.RequestCycleListenerCollection; import org.apache.wicket.request.handler.render.PageRenderer; import org.apache.wicket.request.handler.render.WebPageRenderer; import org.apache.wicket.request.http.WebRequest; import org.apache.wicket.request.http.WebResponse; import org.apache.wicket.request.mapper.mount.IMountedRequestMapper; import org.apache.wicket.request.mapper.mount.Mount; import org.apache.wicket.request.mapper.mount.MountMapper; import org.apache.wicket.request.mapper.mount.MountParameters; import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.request.resource.ResourceReference; import org.apache.wicket.request.resource.caching.FilenameWithVersionResourceCachingStrategy; import org.apache.wicket.request.resource.caching.NoOpResourceCachingStrategy; import org.apache.wicket.request.resource.caching.version.LastModifiedResourceVersion; import org.apache.wicket.resource.loader.IStringResourceLoader; import org.apache.wicket.settings.IExceptionSettings; import org.apache.wicket.settings.IResourceSettings; import org.apache.wicket.util.IContextProvider; import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Bytes; import org.apache.wicket.util.resource.IResourceStream; import org.apache.wicket.util.string.StringValue; import org.apache.wicket.util.string.StringValueConversionException; import org.apache.wicket.util.string.Strings; import org.apache.wicket.util.time.Duration; import org.hippoecm.frontend.diagnosis.DiagnosticsRequestCycleListener; import org.hippoecm.frontend.http.CsrfPreventionRequestCycleListener; import org.hippoecm.frontend.model.JcrHelper; import org.hippoecm.frontend.model.JcrNodeModel; import org.hippoecm.frontend.model.UserCredentials; import org.hippoecm.frontend.observation.JcrObservationManager; import org.hippoecm.frontend.plugin.config.impl.IApplicationFactory; import org.hippoecm.frontend.plugin.config.impl.JcrApplicationFactory; import org.hippoecm.frontend.session.PluginUserSession; import org.hippoecm.frontend.session.UserSession; import org.hippoecm.frontend.settings.GlobalSettings; import org.hippoecm.frontend.util.CmsSessionUtil; import org.hippoecm.frontend.util.RequestUtils; import org.hippoecm.repository.HippoRepository; import org.hippoecm.repository.HippoRepositoryFactory; import org.hippoecm.repository.api.HippoNodeType; import org.hippoecm.repository.api.HippoWorkspace; import org.onehippo.cms7.services.HippoServiceRegistry; import org.onehippo.cms7.services.cmscontext.CmsContextService; import org.onehippo.cms7.services.cmscontext.CmsContextServiceImpl; import org.onehippo.cms7.services.cmscontext.CmsInternalCmsContextService; import org.onehippo.cms7.services.cmscontext.CmsSessionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main extends PluginApplication { static final Logger log = LoggerFactory.getLogger(Main.class); private static final String FRONTEND_PATH = "/" + HippoNodeType.CONFIGURATION_PATH + "/" + HippoNodeType.FRONTEND_PATH; private static final String WHITELISTED_CLASSES_FOR_PACKAGE_RESOURCES = "whitelisted.classes.for.package.resources"; private static final String[] DEFAULT_WHITELISTED_CLASSES_FOR_PACKAGE_RESOURCES = { "org.hippoecm.", "org.apache.wicket.", "org.onehippo.", "wicket.contrib." }; /** * Parameter name of the repository storage directory */ public final static String REPOSITORY_ADDRESS_PARAM = "repository-address"; public final static String REPOSITORY_DIRECTORY_PARAM = "repository-directory"; public final static String REPOSITORY_USERNAME_PARAM = "repository-username"; public final static String REPOSITORY_PASSWORD_PARAM = "repository-password"; public final static String DEFAULT_REPOSITORY_DIRECTORY = "WEB-INF/storage"; public final static String MAXUPLOAD_PARAM = "upload-limit"; public final static String ENCRYPT_URLS = "encrypt-urls"; public final static String OUTPUT_WICKETPATHS = "output-wicketpaths"; public final static String PLUGIN_APPLICATION_NAME_PARAMETER = "config"; public final static String PLUGIN_APPLICATION_VALUE_CMS = "cms"; public final static String PLUGIN_APPLICATION_VALUE_CONSOLE = "console"; // comma separated init parameter public final static String ACCEPTED_ORIGIN_WHITELIST = "accepted-origin-whitelist"; /** * Custom Wicket {@link IRequestCycleListener} class names parameter which can be comma or whitespace-separated * string to set multiple {@link IRequestCycleListener}s. */ public final static String REQUEST_CYCLE_LISTENERS_PARAM = "wicket.request.cycle.listeners"; /** * Wicket RequestCycleSettings timeout configuration parameter name in development mode. */ public final static String DEVELOPMENT_REQUEST_TIMEOUT_PARAM = "wicket.development.request.timeout"; /** * Default Wicket RequestCycleSettings timeout milliseconds in development mode. */ public final static long DEFAULT_DEVELOPMENT_REQUEST_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes /** * Wicket RequestCycleSettings timeout configuration parameter name in deployment mode. */ public final static String DEPLOYMENT_REQUEST_TIMEOUT_PARAM = "wicket.deployment.request.timeout"; // class in the root package, to make it possible to use the caching resource stream locator // for resources that are not associated with a class. private static final Class<?> CACHING_RESOURCE_STREAM_LOCATOR_CLASS; static { try { CACHING_RESOURCE_STREAM_LOCATOR_CLASS = Class.forName("CachingResourceStreamLocatorBaseKey"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private HippoRepository repository; private CmsContextServiceImpl cmsContextServiceImpl; private CmsInternalCmsContextService cmsContextService; @Override protected void init() { super.init(); addRequestCycleListeners(); registerSessionListeners(); getPageSettings().setVersionPagesByDefault(false); // getPageSettings().setAutomaticMultiWindowSupport(false); // getSessionSettings().setPageMapEvictionStrategy(new LeastRecentlyAccessedEvictionStrategy(1)); getApplicationSettings().setPageExpiredErrorPage(PageExpiredErrorPage.class); try { String cfgParam = getConfigurationParameter(MAXUPLOAD_PARAM, null); if (cfgParam != null && cfgParam.trim().length() > 0) { getApplicationSettings().setDefaultMaximumUploadSize(Bytes.valueOf(cfgParam)); } } catch (StringValueConversionException ex) { log.warn("Unable to parse number as specified by " + MAXUPLOAD_PARAM, ex); } final IClassResolver originalResolver = getApplicationSettings().getClassResolver(); getApplicationSettings().setClassResolver(new IClassResolver() { @Override public Class resolveClass(String name) throws ClassNotFoundException { if (Session.exists()) { UserSession session = UserSession.get(); ClassLoader loader = session.getClassLoader(); if (loader != null) { return session.getClassLoader().loadClass(name); } } return originalResolver.resolveClass(name); } @Override public Iterator<URL> getResources(String name) { List<URL> resources = new LinkedList<>(); for (Iterator<URL> iter = originalResolver.getResources(name); iter.hasNext();) { resources.add(iter.next()); } if (Session.exists()) { UserSession session = UserSession.get(); ClassLoader loader = session.getClassLoader(); if (loader != null) { try { for (Enumeration<URL> resourceEnum = session.getClassLoader() .getResources(name); resourceEnum.hasMoreElements();) { resources.add(resourceEnum.nextElement()); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return resources.iterator(); } @Override public ClassLoader getClassLoader() { return Main.class.getClassLoader(); } }); final IResourceSettings resourceSettings = getResourceSettings(); // replace current loaders with own list, starting with component-specific List<IStringResourceLoader> loaders = resourceSettings.getStringResourceLoaders(); loaders.add(new ClassFromKeyStringResourceLoader()); loaders.add(new IStringResourceLoader() { @Override public String loadStringResource(final Class<?> clazz, final String key, final Locale locale, final String style, final String variation) { return null; } @Override public String loadStringResource(final Component component, String key, final Locale locale, final String style, final String variation) { if (key.contains(",")) { key = key.substring(0, key.lastIndexOf(',')); return resourceSettings.getLocalizer().getStringIgnoreSettings(key, component, null, locale, style, variation); } return null; } }); if (RuntimeConfigurationType.DEVELOPMENT.equals(getConfigurationType())) { resourceSettings.setCachingStrategy(new NoOpResourceCachingStrategy()); } else { resourceSettings.setCachingStrategy( new FilenameWithVersionResourceCachingStrategy(new LastModifiedResourceVersion())); } mount(new MountMapper("binaries", new IMountedRequestMapper() { @Override public IRequestHandler mapRequest(final Request request, final MountParameters mountParams) { String path = Strings.join("/", request.getUrl().getSegments()); try { javax.jcr.Session subSession = UserSession.get().getJcrSession(); Node node = ((HippoWorkspace) subSession.getWorkspace()).getHierarchyResolver() .getNode(subSession.getRootNode(), path); // YUCK: no exception! if (node == null) { log.info("no binary found at " + path); } else { if (node.isNodeType(HippoNodeType.NT_DOCUMENT)) { node = (Node) JcrHelper.getPrimaryItem(node); } return new JcrResourceRequestHandler(node); } } catch (PathNotFoundException e) { log.info("binary not found " + e.getMessage()); } catch (javax.jcr.LoginException ex) { log.warn(ex.getMessage()); } catch (RepositoryException ex) { log.error(ex.getMessage()); } return null; } @Override public int getCompatibilityScore(final Request request) { return 1; } @Override public Mount mapHandler(final IRequestHandler requestHandler) { return null; } })); String applicationName = getPluginApplicationName(); if (PLUGIN_APPLICATION_VALUE_CMS.equals(applicationName)) { // the following is only applicable and needed for the CMS application, not the Console /* * HST SAML kind of authentication handler needed for Template Composer integration * */ cmsContextService = (CmsInternalCmsContextService) HippoServiceRegistry .getService(CmsContextService.class); if (cmsContextService == null) { cmsContextServiceImpl = new CmsContextServiceImpl(); cmsContextService = cmsContextServiceImpl; HippoServiceRegistry.registerService(cmsContextServiceImpl, new Class[] { CmsContextService.class, CmsInternalCmsContextService.class }); } mount(new MountMapper("auth", new IMountedRequestMapper() { @Override public IRequestHandler mapRequest(final Request request, final MountParameters mountParams) { IRequestHandler requestTarget = new RenderPageRequestHandler( new PageProvider(getHomePage(), null), RedirectPolicy.AUTO_REDIRECT); IRequestParameters requestParameters = request.getRequestParameters(); final List<StringValue> cmsCSIDParams = requestParameters.getParameterValues("cmsCSID"); final List<StringValue> destinationPathParams = requestParameters .getParameterValues("destinationPath"); final String destinationPath = destinationPathParams != null && !destinationPathParams.isEmpty() ? destinationPathParams.get(0).toString() : null; PluginUserSession userSession = (PluginUserSession) Session.get(); final UserCredentials userCredentials = userSession.getUserCredentials(); HttpSession httpSession = ((ServletWebRequest) request).getContainerRequest().getSession(); final CmsSessionContext cmsSessionContext = CmsSessionContext.getContext(httpSession); if (destinationPath != null && destinationPath.startsWith("/") && (cmsSessionContext != null || userCredentials != null)) { requestTarget = new IRequestHandler() { @Override public void respond(IRequestCycle requestCycle) { String destinationUrl = RequestUtils.getFarthestUrlPrefix(request) + destinationPath; WebResponse response = (WebResponse) RequestCycle.get().getResponse(); String cmsCSID = cmsCSIDParams == null ? null : cmsCSIDParams.get(0) == null ? null : cmsCSIDParams.get(0).toString(); if (!cmsContextService.getId().equals(cmsCSID)) { // redirect to destinationURL and include marker that it is a retry. This way // the destination can choose to not redirect for SSO handshake again if it still does not // have a key if (destinationUrl.contains("?")) { response.sendRedirect(destinationUrl + "&retry"); } else { response.sendRedirect(destinationUrl + "?retry"); } return; } String cmsSessionContextId = cmsSessionContext != null ? cmsSessionContext.getId() : null; if (cmsSessionContextId == null) { CmsSessionContext newCmsSessionContext = cmsContextService.create(httpSession); CmsSessionUtil.populateCmsSessionContext(cmsContextService, newCmsSessionContext, userSession); cmsSessionContextId = newCmsSessionContext.getId(); } if (destinationUrl.contains("?")) { response.sendRedirect(destinationUrl + "&cmsCSID=" + cmsContextService.getId() + "&cmsSCID=" + cmsSessionContextId); } else { response.sendRedirect(destinationUrl + "?cmsCSID=" + cmsContextService.getId() + "&cmsSCID=" + cmsSessionContextId); } } @Override public void detach(IRequestCycle requestCycle) { //Nothing to detach. } }; } return requestTarget; } @Override public int getCompatibilityScore(final Request request) { return 0; } @Override public Mount mapHandler(final IRequestHandler requestHandler) { return null; } })); } // caching resource stream locator implementation that allows the class argument to be null. final IResourceStreamLocator resourceStreamLocator = resourceSettings.getResourceStreamLocator(); resourceSettings.setResourceStreamLocator(new IResourceStreamLocator() { @Override public IResourceStream locate(Class<?> clazz, final String path) { if (clazz == null) { clazz = CACHING_RESOURCE_STREAM_LOCATOR_CLASS; } return resourceStreamLocator.locate(clazz, path); } @Override public IResourceStream locate(Class<?> clazz, final String path, final String style, final String variation, final Locale locale, final String extension, final boolean strict) { if (clazz == null) { clazz = CACHING_RESOURCE_STREAM_LOCATOR_CLASS; } return resourceStreamLocator.locate(clazz, path, style, variation, locale, extension, strict); } @Override public IResourceNameIterator newResourceNameIterator(final String path, final Locale locale, final String style, final String variation, final String extension, final boolean strict) { return resourceStreamLocator.newResourceNameIterator(path, locale, style, variation, extension, strict); } }); if (RuntimeConfigurationType.DEVELOPMENT.equals(getConfigurationType())) { // disable cache resourceSettings.getLocalizer().setEnableCache(false); final long timeout = NumberUtils.toLong( getConfigurationParameter(DEVELOPMENT_REQUEST_TIMEOUT_PARAM, null), DEFAULT_DEVELOPMENT_REQUEST_TIMEOUT_MS); if (timeout > 0L) { log.info("Setting wicket request timeout to {} ms.", timeout); getRequestCycleSettings().setTimeout(Duration.milliseconds(timeout)); } // render comments with component class names getDebugSettings().setOutputMarkupContainerClassName(true); // do not render Wicket-specific markup since it can break CSS getMarkupSettings().setStripWicketTags(true); } else { // don't serialize pages for performance setPageManagerProvider(new DefaultPageManagerProvider(this) { @Override protected IPageStore newPageStore(final IDataStore dataStore) { return new AmnesicPageStore(); } }); // don't throw on missing resource resourceSettings.setThrowExceptionOnMissingResource(false); // don't show exception page getExceptionSettings().setUnexpectedExceptionDisplay(IExceptionSettings.SHOW_NO_EXCEPTION_PAGE); final long timeout = NumberUtils .toLong(getConfigurationParameter(DEPLOYMENT_REQUEST_TIMEOUT_PARAM, null)); if (timeout > 0L) { log.info("Setting wicket request timeout to {} ms.", timeout); getRequestCycleSettings().setTimeout(Duration.milliseconds(timeout)); } } String outputWicketpaths = obtainOutputWicketPathsParameter(); if (outputWicketpaths != null && "true".equalsIgnoreCase(outputWicketpaths)) { getDebugSettings().setOutputComponentPath(true); } final IContextProvider<AjaxRequestTarget, Page> ajaxRequestTargetProvider = getAjaxRequestTargetProvider(); setAjaxRequestTargetProvider(context -> new PluginRequestTarget(ajaxRequestTargetProvider.get(context))); setPageRendererProvider(new IPageRendererProvider() { @Override public PageRenderer get(final RenderPageRequestHandler context) { return new WebPageRenderer(context) { @Override protected BufferedWebResponse renderPage(final Url targetUrl, final RequestCycle requestCycle) { IRequestHandler scheduled = requestCycle.getRequestHandlerScheduledAfterCurrent(); if (scheduled == null) { IRequestablePage page = getPage(); if (page instanceof Home) { Home home = (Home) page; home.processEvents(); home.render(null); } } return super.renderPage(targetUrl, requestCycle); } }; } }); // don't allow public access to any package resource (empty whitelist) by default resourceSettings.setPackageResourceGuard(new WhitelistedClassesResourceGuard()); if (log.isInfoEnabled()) { log.info("Hippo CMS application " + applicationName + " has started"); } } protected void initPackageResourceGuard() { final WhitelistedClassesResourceGuard packageResourceGuard = new WhitelistedClassesResourceGuard(); String[] classNamePrefixes = GlobalSettings.get().getStringArray(WHITELISTED_CLASSES_FOR_PACKAGE_RESOURCES); if (classNamePrefixes == null || classNamePrefixes.length == 0) { log.info("No whitelisted package resources found, using the default whitelist: {}", Arrays.asList(DEFAULT_WHITELISTED_CLASSES_FOR_PACKAGE_RESOURCES)); classNamePrefixes = DEFAULT_WHITELISTED_CLASSES_FOR_PACKAGE_RESOURCES; } packageResourceGuard.addClassNamePrefixes(classNamePrefixes); // CMS7-8898: allow .woff2 files to be served packageResourceGuard.addPattern("+*.woff2"); getResourceSettings().setPackageResourceGuard(packageResourceGuard); } protected void registerSessionListeners() { getSessionListeners().add(session -> { ((PluginUserSession) session).login(); initPackageResourceGuard(); }); } @Override public void internalDestroy() { super.internalDestroy(); if (log.isInfoEnabled()) { String applicationName = getPluginApplicationName(); log.info("Hippo CMS application " + applicationName + " has stopped"); } } /** * Tries to get the output wicket paths parameter from: * <ol> * <li>The servlet init parameter</li> * <li>The servlet context if the init parameter isn't set</li> * </ol> * @return the value of the output wicket paths parameter */ private String obtainOutputWicketPathsParameter() { String outputWicketpaths = getInitParameter(OUTPUT_WICKETPATHS); if (outputWicketpaths == null) { outputWicketpaths = getServletContext().getInitParameter(OUTPUT_WICKETPATHS); } return outputWicketpaths; } @Override public String getPluginApplicationName() { return getConfigurationParameter(PLUGIN_APPLICATION_NAME_PARAMETER, PLUGIN_APPLICATION_VALUE_CMS); } @Override public String getConfigurationParameter(String parameterName, String defaultValue) { String result = getInitParameter(parameterName); if (result == null || result.equals("")) { result = getServletContext().getInitParameter(parameterName); } if (result == null || result.equals("")) { result = defaultValue; } return result; } @Override public ResourceReference getPluginApplicationFavIconReference() { return new PackageResourceReference(Main.class, "hippo-" + getPluginApplicationName() + ".ico"); } @Override public Class<PluginPage> getHomePage() { return org.hippoecm.frontend.PluginPage.class; } // ease testing by making page manager context available in the package @Override protected IPageManagerContext getPageManagerContext() { return super.getPageManagerContext(); } @Override protected WebResponse newWebResponse(final WebRequest webRequest, final HttpServletResponse httpServletResponse) { return new ResponseSplittingProtectingServletWebResponse(webRequest, httpServletResponse); } @Override public UserSession newSession(Request request, Response response) { return new PluginUserSession(request); } public IApplicationFactory getApplicationFactory(final javax.jcr.Session jcrSession) { return new JcrApplicationFactory(new JcrNodeModel(FRONTEND_PATH)); } public HippoRepository getRepository() throws RepositoryException { if (repository == null) { String repositoryAddress = getConfigurationParameter(REPOSITORY_ADDRESS_PARAM, null); String repositoryDirectory = getConfigurationParameter(REPOSITORY_DIRECTORY_PARAM, DEFAULT_REPOSITORY_DIRECTORY); if (repositoryAddress != null && !repositoryAddress.trim().equals("")) { repository = HippoRepositoryFactory.getHippoRepository(repositoryAddress); } else { repository = HippoRepositoryFactory.getHippoRepository(repositoryDirectory); } String repositoryUsername = getConfigurationParameter(REPOSITORY_USERNAME_PARAM, null); String repositoryPassword = getConfigurationParameter(REPOSITORY_PASSWORD_PARAM, null); PluginUserSession.setCredentials(new UserCredentials(repositoryUsername, repositoryPassword)); } return repository; } public void resetConnection() { repository = null; } @Override protected void onDestroy() { if (cmsContextServiceImpl != null) { HippoServiceRegistry.unregisterService(cmsContextServiceImpl, CmsContextService.class); cmsContextServiceImpl = null; } cmsContextService = null; if (repository != null) { // remove listeners JcrObservationManager jom = JcrObservationManager.getInstance(); EventListenerIterator eli; try { eli = jom.getRegisteredEventListeners(); log.info("Number of listeners to remove: {}", eli.getSize()); while (eli.hasNext()) { EventListener el = eli.nextEventListener(); try { jom.removeEventListener(el); } catch (RepositoryException e) { log.warn("Unable to remove listener", e); } } } catch (RepositoryException e) { log.error("Unable to get registered event listeners for shutdown", e); } // close repository.close(); repository = null; } } /** * Adds the default built-in {@link IRequestCycleListener} or configured custom {@link IRequestCycleListener}s. Note that the * default <code>CsrfPreventionRequestCycleListener</code> always gets added, regardless whether custom {@link IRequestCycleListener}s * are configured. * <P> * If no custom {@link IRequestCycleListener}s are configured, then this simply registers the default built-in * listeners such as {@link org.hippoecm.frontend.diagnosis.DiagnosticsRequestCycleListener} and {@link RepositoryRuntimeExceptionHandlingRequestCycleListener}. * Otherwise, this registers only the custom configured {@link IRequestCycleListener}s. * </P> */ private void addRequestCycleListeners() { String[] listenerClassNames = StringUtils .split(getConfigurationParameter(REQUEST_CYCLE_LISTENERS_PARAM, null), " ,\t\r\n"); RequestCycleListenerCollection requestCycleListenerCollection = getRequestCycleListeners(); addCsrfPreventionRequestCycleListener(requestCycleListenerCollection); if (listenerClassNames == null || listenerClassNames.length == 0) { requestCycleListenerCollection.add(new DiagnosticsRequestCycleListener()); requestCycleListenerCollection.add(new RepositoryRuntimeExceptionHandlingRequestCycleListener()); } else { for (String listenerClassName : listenerClassNames) { try { Class<?> clazz = Class.forName(listenerClassName); IRequestCycleListener listener = (IRequestCycleListener) clazz.newInstance(); requestCycleListenerCollection.add(listener); } catch (Throwable th) { log.error("Failed to register RequestCycleListener, " + listenerClassName, th); } } } } private void addCsrfPreventionRequestCycleListener( final RequestCycleListenerCollection requestCycleListenerCollection) { final CsrfPreventionRequestCycleListener listener = new CsrfPreventionRequestCycleListener(); // split on tab (\t), line feed (\n), carriage return (\r), form feed (\f), " ", and "," final String[] acceptedOrigins = StringUtils .split(getConfigurationParameter(ACCEPTED_ORIGIN_WHITELIST, null), " ,\t\f\r\n"); if (acceptedOrigins != null && acceptedOrigins.length > 0) { for (String acceptedOrigin : acceptedOrigins) { listener.addAcceptedOrigin(acceptedOrigin); } } requestCycleListenerCollection.add(listener); } private static class ResponseSplittingProtectingServletWebResponse extends ServletWebResponse { public ResponseSplittingProtectingServletWebResponse(final WebRequest webRequest, final HttpServletResponse httpServletResponse) { super((ServletWebRequest) webRequest, httpServletResponse); } @Override public void addHeader(final String name, final String value) { if (containsCRorLF(value)) { throw new IllegalArgumentException("Header value must not contain CR or LF characters"); } super.addHeader(name, value); } @Override public void setHeader(final String name, final String value) { if (containsCRorLF(value)) { throw new IllegalArgumentException("Header value must not contain CR or LF characters"); } super.setHeader(name, value); } @Override public void sendRedirect(String url) { Args.notNull(url, "url"); if (containsCRorLF(url)) { throw new IllegalArgumentException( "CR or LF detected in redirect URL: possible http response splitting attack"); } if (url.equals("./")) { url += "?"; } super.sendRedirect(url); } private boolean containsCRorLF(String s) { int length = s.length(); for (int i = 0; i < length; ++i) { char c = s.charAt(i); if ('\n' == c || '\r' == c) { return true; } } return false; } } }