| /******************************************************************************* |
| * Copyright (c) 2011, 2019 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Raymond Augé <raymond.auge@liferay.com> - Bug 436698 |
| *******************************************************************************/ |
| package org.eclipse.equinox.http.servlet.internal.registration; |
| |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.regex.Pattern; |
| import javax.servlet.*; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import org.eclipse.equinox.http.servlet.internal.context.ContextController; |
| import org.eclipse.equinox.http.servlet.internal.context.ServiceHolder; |
| import org.eclipse.equinox.http.servlet.internal.servlet.FilterChainImpl; |
| import org.eclipse.equinox.http.servlet.internal.servlet.Match; |
| import org.eclipse.equinox.http.servlet.internal.util.Const; |
| import org.osgi.framework.FrameworkUtil; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.wiring.BundleWiring; |
| import org.osgi.service.http.runtime.dto.FilterDTO; |
| |
| //This class wraps the filter object registered in the HttpService.registerFilter call, to manage the context classloader when handleRequests are being asked. |
| public class FilterRegistration |
| extends MatchableRegistration<Filter, FilterDTO> |
| implements Comparable<FilterRegistration> { |
| |
| private final ServiceHolder<Filter> filterHolder; |
| private final ClassLoader classLoader; |
| private final int priority; |
| private final ContextController contextController; |
| private final boolean initDestoyWithContextController; |
| private final Pattern[] compiledRegexs; |
| private final boolean needDecode; |
| |
| public FilterRegistration( |
| ServiceHolder<Filter> filterHolder, FilterDTO filterDTO, int priority, |
| ContextController contextController) { |
| |
| super(filterHolder.get(), filterDTO); |
| this.filterHolder = filterHolder; |
| this.priority = priority; |
| this.contextController = contextController; |
| this.compiledRegexs = getCompiledRegex(filterDTO); |
| if (filterHolder.getLegacyTCCL() != null) { |
| // legacy filter registrations used the current TCCL at registration time |
| classLoader = filterHolder.getLegacyTCCL(); |
| } else { |
| classLoader = filterHolder.getBundle().adapt(BundleWiring.class).getClassLoader(); |
| } |
| String legacyContextFilter = (String) filterHolder.getServiceReference().getProperty(Const.EQUINOX_LEGACY_CONTEXT_SELECT); |
| if (legacyContextFilter != null) { |
| // This is a legacy Filter registration. |
| // This filter tells us the real context controller, |
| // backed by an HttpContext that should be used to init/destroy this Filter |
| org.osgi.framework.Filter f = null; |
| try { |
| f = FrameworkUtil.createFilter(legacyContextFilter); |
| } |
| catch (InvalidSyntaxException e) { |
| // nothing |
| } |
| initDestoyWithContextController = f == null || contextController.matches(f); |
| } else { |
| initDestoyWithContextController = true; |
| } |
| needDecode = MatchableRegistration.patternsRequireDecode(filterDTO.patterns); |
| } |
| |
| @Override |
| public int compareTo(FilterRegistration otherFilterRegistration) { |
| int priorityDifference = priority - otherFilterRegistration.priority; |
| if (priorityDifference != 0) |
| return -priorityDifference; |
| |
| // Note that we use abs here because the DTO service ID may have been negated for legacy filters. |
| // We always compare with the positive id values and we know the positive values are unique. |
| long thisId = Math.abs(getD().serviceId); |
| long otherId = Math.abs(otherFilterRegistration.getD().serviceId); |
| return (thisId < otherId) ? -1 : ((thisId == otherId) ? 0 : 1); |
| } |
| |
| @Override |
| public void destroy() { |
| if (!initDestoyWithContextController) { |
| return; |
| } |
| ClassLoader original = Thread.currentThread().getContextClassLoader(); |
| try { |
| Thread.currentThread().setContextClassLoader(classLoader); |
| contextController.getHttpServiceRuntime().getRegisteredObjects().remove(this.getT()); |
| contextController.getFilterRegistrations().remove(this); |
| contextController.ungetServletContextHelper(filterHolder.getBundle()); |
| super.destroy(); |
| getT().destroy(); |
| } |
| finally { |
| destroyContextAttributes(); |
| Thread.currentThread().setContextClassLoader(original); |
| filterHolder.release(); |
| } |
| } |
| |
| public boolean appliesTo(FilterChainImpl filterChainImpl) { |
| return (Arrays.binarySearch( |
| getD().dispatcher, filterChainImpl.getDispatcherType().name()) >= 0); |
| } |
| |
| //Delegate the handling of the request to the actual filter |
| public void doFilter( |
| HttpServletRequest request, HttpServletResponse response, |
| FilterChain chain) |
| throws IOException, ServletException { |
| |
| ClassLoader original = Thread.currentThread().getContextClassLoader(); |
| try { |
| Thread.currentThread().setContextClassLoader(classLoader); |
| getT().doFilter(request, response, chain); |
| } |
| finally { |
| Thread.currentThread().setContextClassLoader(original); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof FilterRegistration)) { |
| return false; |
| } |
| |
| FilterRegistration filterRegistration = (FilterRegistration)obj; |
| |
| return getT().equals(filterRegistration.getT()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Long.valueOf(getD().serviceId).hashCode(); |
| } |
| |
| //Delegate the init call to the actual filter |
| public void init(FilterConfig filterConfig) throws ServletException { |
| if (!initDestoyWithContextController) { |
| return; |
| } |
| boolean initialized = false; |
| ClassLoader original = Thread.currentThread().getContextClassLoader(); |
| try { |
| Thread.currentThread().setContextClassLoader(classLoader); |
| |
| createContextAttributes(); |
| getT().init(filterConfig); |
| initialized = true; |
| } |
| finally { |
| if (!initialized) { |
| destroyContextAttributes(); |
| } |
| Thread.currentThread().setContextClassLoader(original); |
| } |
| } |
| |
| public String match( |
| String name, String requestURI, String extension, Match match) { |
| if ((name != null) && (getD().servletNames != null)) { |
| for (String servletName : getD().servletNames) { |
| if (servletName.equals(name)) { |
| return name; |
| } |
| } |
| } |
| |
| if (requestURI == null || requestURI.isEmpty()) { |
| return null; |
| } |
| |
| for (String pattern : getD().patterns) { |
| if (doPatternMatch(pattern, requestURI, extension)) { |
| return pattern; |
| } |
| } |
| |
| for (Pattern regex : compiledRegexs) { |
| if (regex.matcher(requestURI).matches()) { |
| return regex.toString(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public String match( |
| String name, String servletPath, String pathInfo, String extension, Match match) { |
| // TODO need to rework match for filters to remove this method |
| throw new UnsupportedOperationException("Should not be calling this method on FilterRegistration"); //$NON-NLS-1$ |
| } |
| |
| private void createContextAttributes() { |
| contextController.createContextAttributes(); |
| } |
| |
| private void destroyContextAttributes() { |
| contextController.destroyContextAttributes(); |
| } |
| |
| protected boolean isPathWildcardMatch(String pattern, String path) { |
| if (path == null) { |
| return false; |
| } |
| // first try wild card matching if the pattern requests it |
| if (pattern.endsWith("/*")) { //$NON-NLS-1$ |
| int pathPatternLength = pattern.length() - 2; |
| if (path.regionMatches(0, pattern, 0, pathPatternLength)) { |
| return path.length() <= pathPatternLength || path.charAt(pathPatternLength) == '/'; |
| } |
| return false; |
| } |
| // now do exact matching |
| return pattern.equals(path); |
| } |
| |
| protected boolean doPatternMatch(String pattern, String path, String extension) |
| throws IllegalArgumentException { |
| |
| if (pattern.indexOf(Const.SLASH_STAR_DOT) == 0) { |
| pattern = pattern.substring(1); |
| } |
| int extensionMatchIndex = pattern.indexOf(Const.SLASH_STAR_DOT); |
| String extensionWithPrefixMatch = null; |
| if (extensionMatchIndex >= 0 && pattern.lastIndexOf('/') == extensionMatchIndex) { |
| extensionWithPrefixMatch = pattern.substring(extensionMatchIndex + 3); |
| pattern = pattern.substring(0, extensionMatchIndex + 2); |
| } |
| |
| if (pattern.isEmpty() && Const.SLASH.equals(path)) { |
| return true; |
| } |
| else if (!pattern.isEmpty()) { |
| // first try prefix path matching; taking into account wild cards if necessary |
| if (pattern.charAt(0) == '/') { |
| if (isPathWildcardMatch(pattern, path)) { |
| if (extensionWithPrefixMatch != null) { |
| return extensionWithPrefixMatch.equals(extension); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| // next try extension matching if requested |
| if (pattern.charAt(0) == '*') { |
| return pattern.substring(2).equals(extension); |
| } |
| } |
| |
| return false; |
| } |
| |
| private Pattern[] getCompiledRegex(FilterDTO filterDTO) { |
| if (filterDTO.regexs == null) { |
| return new Pattern[0]; |
| } |
| |
| Pattern[] patterns = new Pattern[filterDTO.regexs.length]; |
| |
| for (int i = 0; i < filterDTO.regexs.length; i++) { |
| patterns[i] = Pattern.compile(filterDTO.regexs[i]); |
| } |
| |
| return patterns; |
| } |
| |
| @Override |
| public boolean needDecode() { |
| return needDecode; |
| } |
| |
| } |