blob: 491305aa485e426729c0d30faea3d3125eba650e [file] [log] [blame]
/*******************************************************************************
* 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;
}
}