[WW-4692] Extracts Url encoder/decoder into dedicated interfaces/classes (#626)

* WW-4692 Extracts Url encoder/decoder into dedicated interfaces/classes

* WW-4692 Removes code smells reported by Sonar and adds additional test case

* WW-4692 Adds missing header with license

* WW-4692 Prevents int promotion behaviour

* WW-4692 Drops unused imports

* WW-4692 Fixes int promotion behaviour

* WW-4692 Improves code coverage
This commit is contained in:
Lukasz Lenart
2022-11-02 10:32:13 +01:00
committed by GitHub
parent 34aeeded55
commit 2660aaec56
33 changed files with 691 additions and 4522 deletions
@@ -21,26 +21,16 @@ package com.opensymphony.xwork2.config.providers;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.DefaultActionProxyFactory;
import com.opensymphony.xwork2.DefaultLocaleProviderFactory;
import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.StrutsTextProviderFactory;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.factory.DefaultUnknownHandlerFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.accessor.HttpParametersPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ParameterPropertyAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
import com.opensymphony.xwork2.DefaultTextProvider;
import com.opensymphony.xwork2.DefaultUnknownHandlerManager;
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.StrutsTextProviderFactory;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.TextProviderFactory;
import com.opensymphony.xwork2.UnknownHandlerManager;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
@@ -57,14 +47,7 @@ import com.opensymphony.xwork2.conversion.impl.CollectionConverter;
import com.opensymphony.xwork2.conversion.impl.DateConverter;
import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor;
import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor;
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import org.apache.struts2.components.date.DateFormatter;
import org.apache.struts2.components.date.DateTimeFormatterAdapter;
import org.apache.struts2.components.date.SimpleDateFormatAdapter;
import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor;
import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
import org.apache.struts2.conversion.StrutsTypeConverterCreator;
import org.apache.struts2.conversion.StrutsTypeConverterHolder;
import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
import com.opensymphony.xwork2.conversion.impl.NumberConverter;
import com.opensymphony.xwork2.conversion.impl.StringConverter;
@@ -73,34 +56,45 @@ import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.factory.ActionFactory;
import com.opensymphony.xwork2.factory.ConverterFactory;
import com.opensymphony.xwork2.factory.DefaultActionFactory;
import com.opensymphony.xwork2.factory.StrutsConverterFactory;
import com.opensymphony.xwork2.factory.DefaultInterceptorFactory;
import com.opensymphony.xwork2.factory.DefaultResultFactory;
import com.opensymphony.xwork2.factory.DefaultUnknownHandlerFactory;
import com.opensymphony.xwork2.factory.InterceptorFactory;
import com.opensymphony.xwork2.factory.ResultFactory;
import com.opensymphony.xwork2.factory.StrutsConverterFactory;
import com.opensymphony.xwork2.factory.UnknownHandlerFactory;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Scope;
import com.opensymphony.xwork2.ognl.BeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.ObjectProxy;
import com.opensymphony.xwork2.ognl.OgnlReflectionContextFactory;
import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.HttpParametersPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ObjectAccessor;
import com.opensymphony.xwork2.ognl.accessor.ObjectProxyPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.ParameterPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkCollectionPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkEnumerationAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkIteratorPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMapPropertyAccessor;
import com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor;
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.LocalizedTextProvider;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.OgnlTextParser;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.StrutsLocalizedTextProvider;
import com.opensymphony.xwork2.util.TextParser;
import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.WildcardHelper;
@@ -119,8 +113,15 @@ import com.opensymphony.xwork2.validator.ValidatorFileParser;
import ognl.MethodAccessor;
import ognl.PropertyAccessor;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor;
import org.apache.struts2.conversion.StrutsTypeConverterCreator;
import org.apache.struts2.conversion.StrutsTypeConverterHolder;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.url.StrutsUrlDecoder;
import org.apache.struts2.url.StrutsUrlEncoder;
import org.apache.struts2.url.UrlDecoder;
import org.apache.struts2.url.UrlEncoder;
import java.util.ArrayList;
import java.util.Collection;
@@ -153,88 +154,88 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
@Override
public void register(ContainerBuilder builder, LocatableProperties props)
throws ConfigurationException {
throws ConfigurationException {
builder
.factory(ObjectFactory.class)
.factory(ActionFactory.class, DefaultActionFactory.class)
.factory(ResultFactory.class, DefaultResultFactory.class)
.factory(InterceptorFactory.class, DefaultInterceptorFactory.class)
.factory(com.opensymphony.xwork2.factory.ValidatorFactory.class, com.opensymphony.xwork2.factory.DefaultValidatorFactory.class)
.factory(ConverterFactory.class, StrutsConverterFactory.class)
.factory(UnknownHandlerFactory.class, DefaultUnknownHandlerFactory.class)
.factory(ObjectFactory.class)
.factory(ActionFactory.class, DefaultActionFactory.class)
.factory(ResultFactory.class, DefaultResultFactory.class)
.factory(InterceptorFactory.class, DefaultInterceptorFactory.class)
.factory(com.opensymphony.xwork2.factory.ValidatorFactory.class, com.opensymphony.xwork2.factory.DefaultValidatorFactory.class)
.factory(ConverterFactory.class, StrutsConverterFactory.class)
.factory(UnknownHandlerFactory.class, DefaultUnknownHandlerFactory.class)
.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class, Scope.SINGLETON)
.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
.factory(ActionProxyFactory.class, DefaultActionProxyFactory.class, Scope.SINGLETON)
.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON)
.factory(XWorkConverter.class, Scope.SINGLETON)
.factory(XWorkBasicConverter.class, Scope.SINGLETON)
.factory(ConversionPropertiesProcessor.class, StrutsConversionPropertiesProcessor.class, Scope.SINGLETON)
.factory(ConversionFileProcessor.class, DefaultConversionFileProcessor.class, Scope.SINGLETON)
.factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON)
.factory(TypeConverterCreator.class, StrutsTypeConverterCreator.class, Scope.SINGLETON)
.factory(TypeConverterHolder.class, StrutsTypeConverterHolder.class, Scope.SINGLETON)
.factory(XWorkConverter.class, Scope.SINGLETON)
.factory(XWorkBasicConverter.class, Scope.SINGLETON)
.factory(ConversionPropertiesProcessor.class, StrutsConversionPropertiesProcessor.class, Scope.SINGLETON)
.factory(ConversionFileProcessor.class, DefaultConversionFileProcessor.class, Scope.SINGLETON)
.factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON)
.factory(TypeConverterCreator.class, StrutsTypeConverterCreator.class, Scope.SINGLETON)
.factory(TypeConverterHolder.class, StrutsTypeConverterHolder.class, Scope.SINGLETON)
.factory(FileManager.class, "system", DefaultFileManager.class, Scope.SINGLETON)
.factory(FileManagerFactory.class, DefaultFileManagerFactory.class, Scope.SINGLETON)
.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON)
.factory(ValidatorFactory.class, DefaultValidatorFactory.class, Scope.SINGLETON)
.factory(ValidatorFileParser.class, DefaultValidatorFileParser.class, Scope.SINGLETON)
.factory(PatternMatcher.class, WildcardHelper.class, Scope.SINGLETON)
.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON)
.factory(ReflectionContextFactory.class, OgnlReflectionContextFactory.class, Scope.SINGLETON)
.factory(FileManager.class, "system", DefaultFileManager.class, Scope.SINGLETON)
.factory(FileManagerFactory.class, DefaultFileManagerFactory.class, Scope.SINGLETON)
.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON)
.factory(ValidatorFactory.class, DefaultValidatorFactory.class, Scope.SINGLETON)
.factory(ValidatorFileParser.class, DefaultValidatorFileParser.class, Scope.SINGLETON)
.factory(PatternMatcher.class, WildcardHelper.class, Scope.SINGLETON)
.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON)
.factory(ReflectionContextFactory.class, OgnlReflectionContextFactory.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Object.class.getName(), ObjectAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Iterator.class.getName(), XWorkIteratorPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Enumeration.class.getName(), XWorkEnumerationAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Object.class.getName(), ObjectAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Iterator.class.getName(), XWorkIteratorPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Enumeration.class.getName(), XWorkEnumerationAccessor.class, Scope.SINGLETON)
.factory(UnknownHandlerManager.class, DefaultUnknownHandlerManager.class, Scope.SINGLETON)
.factory(UnknownHandlerManager.class, DefaultUnknownHandlerManager.class, Scope.SINGLETON)
// silly workarounds for ognl since there is no way to flush its caches
.factory(PropertyAccessor.class, List.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, ArrayList.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HashSet.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Set.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HashMap.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Map.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Collection.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, ObjectProxy.class.getName(), ObjectProxyPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HttpParameters.class.getName(), HttpParametersPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Parameter.class.getName(), ParameterPropertyAccessor.class, Scope.SINGLETON)
// silly workarounds for ognl since there is no way to flush its caches
.factory(PropertyAccessor.class, List.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, ArrayList.class.getName(), XWorkListPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HashSet.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Set.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HashMap.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Map.class.getName(), XWorkMapPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Collection.class.getName(), XWorkCollectionPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, ObjectProxy.class.getName(), ObjectProxyPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, HttpParameters.class.getName(), HttpParametersPropertyAccessor.class, Scope.SINGLETON)
.factory(PropertyAccessor.class, Parameter.class.getName(), ParameterPropertyAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, Object.class.getName(), XWorkMethodAccessor.class, Scope.SINGLETON)
.factory(MethodAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON)
.factory(TextParser.class, OgnlTextParser.class, Scope.SINGLETON)
.factory(TextParser.class, OgnlTextParser.class, Scope.SINGLETON)
.factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
.factory(NullHandler.class, Object.class.getName(), InstantiatingNullHandler.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, AnnotationActionValidatorManager.class, Scope.SINGLETON)
.factory(ActionValidatorManager.class, "no-annotations", DefaultActionValidatorManager.class, Scope.SINGLETON)
.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
.factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON)
.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
.factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON)
.factory(TextProviderFactory.class, StrutsTextProviderFactory.class, Scope.SINGLETON)
.factory(LocaleProviderFactory.class, DefaultLocaleProviderFactory.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
.factory(OgnlUtil.class, Scope.SINGLETON)
.factory(CollectionConverter.class, Scope.SINGLETON)
.factory(ArrayConverter.class, Scope.SINGLETON)
.factory(DateConverter.class, Scope.SINGLETON)
.factory(NumberConverter.class, Scope.SINGLETON)
.factory(StringConverter.class, Scope.SINGLETON)
.factory(ExpressionCacheFactory.class, DefaultOgnlExpressionCacheFactory.class, Scope.SINGLETON)
.factory(BeanInfoCacheFactory.class, DefaultOgnlBeanInfoCacheFactory.class, Scope.SINGLETON)
.factory(OgnlUtil.class, Scope.SINGLETON)
.factory(CollectionConverter.class, Scope.SINGLETON)
.factory(ArrayConverter.class, Scope.SINGLETON)
.factory(DateConverter.class, Scope.SINGLETON)
.factory(NumberConverter.class, Scope.SINGLETON)
.factory(StringConverter.class, Scope.SINGLETON)
.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class, Scope.PROTOTYPE)
.factory(AcceptedPatternsChecker.class, DefaultAcceptedPatternsChecker.class, Scope.PROTOTYPE)
.factory(NotExcludedAcceptedPatternsChecker.class, DefaultNotExcludedAcceptedPatternsChecker.class
, Scope.SINGLETON)
.factory(ExcludedPatternsChecker.class, DefaultExcludedPatternsChecker.class, Scope.PROTOTYPE)
.factory(AcceptedPatternsChecker.class, DefaultAcceptedPatternsChecker.class, Scope.PROTOTYPE)
.factory(NotExcludedAcceptedPatternsChecker.class, DefaultNotExcludedAcceptedPatternsChecker.class
, Scope.SINGLETON)
.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON)
.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON)
.factory(DateFormatter.class, "simpleDateFormatter", SimpleDateFormatAdapter.class, Scope.SINGLETON)
.factory(DateFormatter.class, "dateTimeFormatter", DateTimeFormatterAdapter.class, Scope.SINGLETON)
.factory(UrlEncoder.class, StrutsUrlEncoder.class, Scope.SINGLETON)
.factory(UrlDecoder.class, StrutsUrlDecoder.class, Scope.SINGLETON)
;
props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString());
@@ -457,4 +457,7 @@ public final class StrutsConstants {
/** See {@link org.apache.struts2.components.Date#setDateFormatter(DateFormatter)} */
public static final String STRUTS_DATE_FORMATTER = "struts.date.formatter";
public static final String STRUTS_URL_ENCODER = "struts.url.encoder";
public static final String STRUTS_URL_DECODER = "struts.url.decoder";
}
@@ -24,7 +24,6 @@ import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.inject.*;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -66,6 +66,8 @@ import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.url.UrlDecoder;
import org.apache.struts2.url.UrlEncoder;
import org.apache.struts2.util.ContentTypeMatcher;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.util.UrlHelper;
@@ -429,6 +431,9 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
alias(ExpressionCacheFactory.class, StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_FACTORY, builder, props, Scope.SINGLETON);
alias(BeanInfoCacheFactory.class, StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_FACTORY, builder, props, Scope.SINGLETON);
alias(UrlEncoder.class, StrutsConstants.STRUTS_URL_ENCODER, builder, props, Scope.SINGLETON);
alias(UrlDecoder.class, StrutsConstants.STRUTS_URL_DECODER, builder, props, Scope.SINGLETON);
switchDevMode(props);
}
@@ -24,7 +24,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.util.URLDecoderUtil;
import org.apache.struts2.url.UrlDecoder;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@@ -32,18 +32,26 @@ import java.util.StringTokenizer;
/**
* Extended version of {@link RestfulActionMapper}, see documentation for more details
* https://struts.apache.org/core-developers/restful-action-mapper.html
* <a href="https://struts.apache.org/core-developers/restful-action-mapper.html">Restful2ActionMapper</a>
*/
public class Restful2ActionMapper extends DefaultActionMapper {
protected static final Logger LOG = LogManager.getLogger(Restful2ActionMapper.class);
private static final Logger LOG = LogManager.getLogger(Restful2ActionMapper.class);
public static final String HTTP_METHOD_PARAM = "__http_method";
private String idParameterName = null;
private UrlDecoder decoder;
public Restful2ActionMapper() {
setSlashesInActionNames("true");
}
@Inject
public void setDecoder(UrlDecoder decoder) {
this.decoder = decoder;
}
/*
* (non-Javadoc)
*
@@ -54,7 +62,7 @@ public class Restful2ActionMapper extends DefaultActionMapper {
throw new IllegalStateException("This action mapper requires the setting 'slashesInActionNames' to be set to 'true'");
}
ActionMapping mapping = super.getMapping(request, configManager);
if (mapping == null) {
return null;
}
@@ -78,7 +86,7 @@ public class Restful2ActionMapper extends DefaultActionMapper {
// Index e.g. foo/
if (isGet(request)) {
mapping.setMethod("index");
// Creating a new entry on POST e.g. foo/
} else if (isPost(request)) {
mapping.setMethod("create");
@@ -96,14 +104,14 @@ public class Restful2ActionMapper extends DefaultActionMapper {
// Removing an item e.g. foo/1
} else if (isDelete(request)) {
mapping.setMethod("remove");
// Updating an item e.g. foo/1
// Updating an item e.g. foo/1
} else if (isPut(request)) {
mapping.setMethod("update");
}
}
if (idParameterName != null && lastSlashPos > -1) {
actionName = actionName.substring(0, lastSlashPos);
}
@@ -129,10 +137,10 @@ public class Restful2ActionMapper extends DefaultActionMapper {
while (st.hasMoreTokens()) {
if (isNameTok) {
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
paramName = decoder.decode(st.nextToken(), "UTF-8", false);
isNameTok = false;
} else {
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
paramValue = decoder.decode(st.nextToken(), "UTF-8", false);
if ((paramName != null) && (paramName.length() > 0)) {
parameters.put(paramName, paramValue);
@@ -143,7 +151,7 @@ public class Restful2ActionMapper extends DefaultActionMapper {
}
if (parameters.size() > 0) {
if (mapping.getParams() == null) {
mapping.setParams(new HashMap<String, Object>());
mapping.setParams(new HashMap<>());
}
mapping.getParams().putAll(parameters);
}
@@ -19,10 +19,11 @@
package org.apache.struts2.dispatcher.mapper;
import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.util.URLDecoderUtil;
import org.apache.struts2.url.UrlDecoder;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
@@ -32,11 +33,19 @@ import java.util.StringTokenizer;
/**
* Simple Restfull Action Mapper to support REST application
* See docs for more information
* https://struts.apache.org/core-developers/restful-action-mapper.html
* <a href="https://struts.apache.org/core-developers/restful-action-mapper.html">RestfulActionMapper</a>
*/
public class RestfulActionMapper implements ActionMapper {
protected static final Logger LOG = LogManager.getLogger(RestfulActionMapper.class);
private UrlDecoder decoder;
@Inject
public void setDecoder(UrlDecoder decoder) {
this.decoder = decoder;
}
/* (non-Javadoc)
* @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)
*/
@@ -64,10 +73,10 @@ public class RestfulActionMapper implements ActionMapper {
while (st.hasMoreTokens()) {
if (isNameTok) {
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
paramName = decoder.decode(st.nextToken(), "UTF-8", false);
isNameTok = false;
} else {
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
paramValue = decoder.decode(st.nextToken(), "UTF-8", false);
if ((paramName != null) && (paramName.length() > 0)) {
parameters.put(paramName, paramValue);
@@ -98,7 +107,7 @@ public class RestfulActionMapper implements ActionMapper {
if (value != null) {
retVal.append("/");
retVal.append(value);
}
}
return retVal.toString();
}
@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.url;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.struts2.StrutsConstants;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
public class StrutsUrlDecoder implements UrlDecoder {
private static final Logger LOG = LogManager.getLogger(StrutsUrlDecoder.class);
private String encoding = "UTF-8";
@Inject(value = StrutsConstants.STRUTS_I18N_ENCODING, required = false)
public void setEncoding(String encoding) {
LOG.debug("Using default encoding: {}", encoding);
if (StringUtils.isNotEmpty(encoding)) {
this.encoding = encoding;
}
}
@Override
public String decode(String input, String encoding, boolean isQueryString) {
if (input == null) {
return (null);
}
byte[] bytes = null;
try {
bytes = input.getBytes(getCharset(encoding));
} catch (UnsupportedEncodingException uee) {
LOG.debug(new ParameterizedMessage("Unable to URL decode the specified input since the encoding: {} is not supported.", encoding), uee);
}
return internalDecode(bytes, encoding, isQueryString);
}
@Override
public String decode(String input, boolean isQueryString) {
return this.decode(input, this.encoding, isQueryString);
}
@Override
public String decode(String input) {
return decode(input, false);
}
private String internalDecode(byte[] bytes, String encoding, boolean isQuery) {
if (bytes == null) {
return null;
}
int len = bytes.length;
int ix = 0;
int ox = 0;
while (ix < len) {
byte b = bytes[ix++]; // Get byte to test
if (b == '+' && isQuery) {
b = (byte) ' ';
} else if (b == '%') {
if (ix + 2 > len) {
throw new IllegalArgumentException("The % character must be followed by two hexadecimal digits");
}
b = (byte) ((((convertHexDigit(bytes[ix++]) << 4) & 0xff) + convertHexDigit(bytes[ix++]) & 0xff) & 0xff);
}
bytes[ox++] = b;
}
if (encoding != null) {
try {
return new String(bytes, 0, ox, getCharset(encoding));
} catch (UnsupportedEncodingException uee) {
LOG.debug(new ParameterizedMessage("Unable to URL decode the specified input since the encoding: {} is not supported.", encoding), uee);
return null;
}
}
return new String(bytes, 0, ox);
}
private byte convertHexDigit(byte b) {
if ((b >= '0') && (b <= '9')) return (byte) (b - '0');
if ((b >= 'a') && (b <= 'f')) return (byte) (b - 'a' + 10);
if ((b >= 'A') && (b <= 'F')) return (byte) (b - 'A' + 10);
throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit");
}
private Charset getCharset(String encoding) throws UnsupportedEncodingException {
for (Charset charset : Charset.availableCharsets().values()) {
if (encoding.equalsIgnoreCase(charset.name())) {
return charset;
}
}
throw new UnsupportedEncodingException("The character encoding " + encoding + " is not supported");
}
}
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.url;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
public class StrutsUrlEncoder implements UrlEncoder {
private static final Logger LOG = LogManager.getLogger(StrutsUrlEncoder.class);
private String encoding = "UTF-8";
@Inject(value = StrutsConstants.STRUTS_I18N_ENCODING, required = false)
public void setEncoding(String encoding) {
LOG.debug("Using default encoding: {}", encoding);
if (StringUtils.isNotEmpty(encoding)) {
this.encoding = encoding;
}
}
@Override
public String encode(String input, String encoding) {
try {
return URLEncoder.encode(input, encoding);
} catch (UnsupportedEncodingException e) {
LOG.warn("Could not encode URL parameter '{}', returning value un-encoded", input);
return input;
}
}
@Override
public String encode(String input) {
return encode(input, encoding);
}
}
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.url;
public interface UrlDecoder {
/**
* Decodes the input using default encoding, e.g.: struts.i18n.encoding
*
* @param input String to decode
* @param encoding encoding used in decoding
* @param isQueryString indicates if input is a query string
* @return the decoded string
*/
String decode(String input, String encoding, boolean isQueryString);
/**
* Decodes the input using default encoding, e.g.: struts.i18n.encoding
*
* @param input String to decode
* @param isQueryString indicates if input is a query string
* @return the decoded string
*/
String decode(String input, boolean isQueryString);
/**
* Decodes the input using default encoding, e.g.: struts.i18n.encoding
*
* @param input String to decode
* @return the decoded string
*/
String decode(String input);
}
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.url;
public interface UrlEncoder {
/**
* Encodes the input tb be used with URL using the provided encoding
*
* @param input String to encode
* @param encoding encoding to use
* @return encoded string
*/
String encode(String input, String encoding);
/**
* Encodes the input to be used with URL using default encoding, e.g.: struts.i18n.encoding
*
* @param input String to encode
* @return encoded string
*/
String encode(String input);
}
@@ -1,52 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util;
import org.apache.struts2.util.tomcat.buf.UDecoder;
/**
* URLDecoderUtil serves as a facade for a correct URL decoding implementation.
* As of Struts 2.3.25 it uses Tomcat URLDecoder functionality rather than the one found in java.io.
*/
public class URLDecoderUtil {
/**
* Decodes a <code>x-www-form-urlencoded</code> string.
* @param sequence the String to decode
* @param charset The name of a supported character encoding.
* @return the newly decoded <code>String</code>
* @exception IllegalArgumentException If the encoding is not valid
*/
public static String decode(String sequence, String charset) {
return UDecoder.URLDecode(sequence, charset);
}
/**
* Decodes a <code>x-www-form-urlencoded</code> string.
* @param sequence the String to decode
* @param charset The name of a supported character encoding.
* @param isQueryString whether input is a query string. If <code>true</code> other decoding rules apply.
* @return the newly decoded <code>String</code>
* @exception IllegalArgumentException If the encoding is not valid
*/
public static String decode(String sequence, String charset, boolean isQueryString) {
return UDecoder.URLDecode(sequence, charset, isQueryString);
}
}
@@ -1,257 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
/**
* This class implements some basic ASCII character handling functions.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
*/
public final class Ascii {
/*
* Character translation tables.
*/
private static final byte[] toUpper = new byte[256];
private static final byte[] toLower = new byte[256];
/*
* Character type tables.
*/
private static final boolean[] isAlpha = new boolean[256];
private static final boolean[] isUpper = new boolean[256];
private static final boolean[] isLower = new boolean[256];
private static final boolean[] isWhite = new boolean[256];
private static final boolean[] isDigit = new boolean[256];
private static final long OVERFLOW_LIMIT = Long.MAX_VALUE / 10;
/*
* Initialize character translation and type tables.
*/
static {
for (int i = 0; i < 256; i++) {
toUpper[i] = (byte)i;
toLower[i] = (byte)i;
}
for (int lc = 'a'; lc <= 'z'; lc++) {
int uc = lc + 'A' - 'a';
toUpper[lc] = (byte)uc;
toLower[uc] = (byte)lc;
isAlpha[lc] = true;
isAlpha[uc] = true;
isLower[lc] = true;
isUpper[uc] = true;
}
isWhite[ ' '] = true;
isWhite['\t'] = true;
isWhite['\r'] = true;
isWhite['\n'] = true;
isWhite['\f'] = true;
isWhite['\b'] = true;
for (int d = '0'; d <= '9'; d++) {
isDigit[d] = true;
}
}
/**
* Returns the upper case equivalent of the specified ASCII character.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int toUpper(int c) {
return toUpper[c & 0xff] & 0xff;
}
/**
* Returns the lower case equivalent of the specified ASCII character.
*/
public static int toLower(int c) {
return toLower[c & 0xff] & 0xff;
}
/**
* Returns true if the specified ASCII character is upper or lower case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isAlpha(int c) {
return isAlpha[c & 0xff];
}
/**
* Returns true if the specified ASCII character is upper case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isUpper(int c) {
return isUpper[c & 0xff];
}
/**
* Returns true if the specified ASCII character is lower case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isLower(int c) {
return isLower[c & 0xff];
}
/**
* Returns true if the specified ASCII character is white space.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isWhite(int c) {
return isWhite[c & 0xff];
}
/**
* Returns true if the specified ASCII character is a digit.
*/
public static boolean isDigit(int c) {
return isDigit[c & 0xff];
}
/**
* Parses an unsigned integer from the specified subarray of bytes.
* @param b the bytes to parse
* @param off the start offset of the bytes
* @param len the length of the bytes
* @exception NumberFormatException if the integer format was invalid
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int parseInt(byte[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
int n = c - '0';
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
n = n * 10 + c - '0';
}
return n;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int parseInt(char[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
int n = c - '0';
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
n = n * 10 + c - '0';
}
return n;
}
/**
* Parses an unsigned long from the specified subarray of bytes.
* @param b the bytes to parse
* @param off the start offset of the bytes
* @param len the length of the bytes
* @exception NumberFormatException if the long format was invalid
*/
public static long parseLong(byte[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
long n = c - '0';
while (--len > 0) {
if (isDigit(c = b[off++]) &&
(n < OVERFLOW_LIMIT || (n == OVERFLOW_LIMIT && (c - '0') < 8))) {
n = n * 10 + c - '0';
} else {
throw new NumberFormatException();
}
}
return n;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static long parseLong(char[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
long n = c - '0';
long m;
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
m = n * 10 + c - '0';
if (m < n) {
// Overflow
throw new NumberFormatException();
} else {
n = m;
}
}
return n;
}
}
@@ -1,203 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
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.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* NIO based character decoder.
*/
public class B2CConverter {
private static final Map<String, Charset> encodingToCharsetCache =
new HashMap<String, Charset>();
public static final Charset ISO_8859_1;
public static final Charset UTF_8;
// Protected so unit tests can use it
protected static final int LEFTOVER_SIZE = 9;
static {
for (Charset charset: Charset.availableCharsets().values()) {
encodingToCharsetCache.put(
charset.name().toLowerCase(Locale.ENGLISH), charset);
for (String alias : charset.aliases()) {
encodingToCharsetCache.put(
alias.toLowerCase(Locale.ENGLISH), charset);
}
}
Charset iso88591 = null;
Charset utf8 = null;
try {
iso88591 = getCharset("ISO-8859-1");
utf8 = getCharset("UTF-8");
} catch (UnsupportedEncodingException e) {
// Impossible. All JVMs must support these.
e.printStackTrace();
}
ISO_8859_1 = iso88591;
UTF_8 = utf8;
}
public static Charset getCharset(String enc)
throws UnsupportedEncodingException {
// Encoding names should all be ASCII
String lowerCaseEnc = enc.toLowerCase(Locale.ENGLISH);
return getCharsetLower(lowerCaseEnc);
}
/**
* Only to be used when it is known that the encoding name is in lower case.
*/
public static Charset getCharsetLower(String lowerCaseEnc)
throws UnsupportedEncodingException {
Charset charset = encodingToCharsetCache.get(lowerCaseEnc);
if (charset == null) {
// Pre-population of the cache means this must be invalid
throw new UnsupportedEncodingException("The character encoding " + lowerCaseEnc + " is not supported");
}
return charset;
}
private final CharsetDecoder decoder;
private ByteBuffer bb = null;
private CharBuffer cb = null;
/**
* Leftover buffer used for incomplete characters.
*/
private final ByteBuffer leftovers;
public B2CConverter(String encoding) throws IOException {
this(encoding, false);
}
public B2CConverter(String encoding, boolean replaceOnError)
throws IOException {
byte[] left = new byte[LEFTOVER_SIZE];
leftovers = ByteBuffer.wrap(left);
CodingErrorAction action;
if (replaceOnError) {
action = CodingErrorAction.REPLACE;
} else {
action = CodingErrorAction.REPORT;
}
Charset charset = getCharset(encoding);
// Special case. Use the Apache Harmony based UTF-8 decoder because it
// - a) rejects invalid sequences that the JVM decoder does not
// - b) fails faster for some invalid sequences
if (charset.equals(UTF_8)) {
decoder = new Utf8Decoder();
} else {
decoder = charset.newDecoder();
}
decoder.onMalformedInput(action);
decoder.onUnmappableCharacter(action);
}
/**
* Reset the decoder state.
*/
public void recycle() {
decoder.reset();
leftovers.position(0);
}
/**
* Convert the given bytes to characters.
*
* @param bc byte input
* @param cc char output
* @param endOfInput Is this all of the available data
*/
public void convert(ByteChunk bc, CharChunk cc, boolean endOfInput)
throws IOException {
if ((bb == null) || (bb.array() != bc.getBuffer())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength());
} else {
// Initialize the byte buffer
bb.limit(bc.getEnd());
bb.position(bc.getStart());
}
if ((cb == null) || (cb.array() != cc.getBuffer())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.getBuffer(), cc.getEnd(),
cc.getBuffer().length - cc.getEnd());
} else {
// Initialize the char buffer
cb.limit(cc.getBuffer().length);
cb.position(cc.getEnd());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = cb.position();
// Loop until one char is decoded or there is a decoder error
do {
leftovers.put(bc.substractB());
leftovers.flip();
result = decoder.decode(leftovers, cb, endOfInput);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (cb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
bb.position(bc.getStart());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = decoder.decode(bb, cb, endOfInput);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk, if
// this continues the char buffer will get resized
bc.setOffset(bb.position());
cc.setEnd(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.setOffset(bb.position());
cc.setEnd(cb.position());
// Put leftovers in the leftovers byte buffer
if (bc.getLength() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(bc.getLength());
bc.substract(leftovers.array(), 0, bc.getLength());
}
}
}
}
@@ -1,937 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
/*
* In a server it is very important to be able to operate on
* the original byte[] without converting everything to chars.
* Some protocols are ASCII only, and some allow different
* non-UNICODE encodings. The encoding is not known beforehand,
* and can even change during the execution of the protocol.
* ( for example a multipart message may have parts with different
* encoding )
*
* For HTTP it is not very clear how the encoding of RequestURI
* and mime values can be determined, but it is a great advantage
* to be able to parse the request without converting to string.
*/
// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
// inside this way it could provide the search/etc on ByteBuffer, as a helper.
/**
* This class is used to represent a chunk of bytes, and
* utilities to manipulate byte[].
*
* The buffer can be modified and used for both input and output.
*
* There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
* or ByteOutputChannel, which will be used when the buffer is empty (on input)
* or filled (on output).
* For output, it can also grow. This operating mode is selected by calling
* setLimit() or allocate(initial, limit) with limit != -1.
*
* Various search and append method are defined - similar with String and
* StringBuffer, but operating on bytes.
*
* This is important because it allows processing the http headers directly on
* the received bytes, without converting to chars and Strings until the strings
* are needed. In addition, the charset is determined later, from headers or
* user code.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class ByteChunk implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/** Input interface, used when the buffer is empty
*
* Same as java.nio.channel.ReadableByteChannel
*/
public static interface ByteInputChannel {
/**
* Read new bytes ( usually the internal conversion buffer ).
* The implementation is allowed to ignore the parameters,
* and mutate the chunk if it wishes to implement its own buffering.
*/
public int realReadBytes(byte cbuf[], int off, int len)
throws IOException;
}
/** Same as java.nio.channel.WrittableByteChannel.
*/
public static interface ByteOutputChannel {
/**
* Send the bytes ( usually the internal conversion buffer ).
* Expect 8k output if the buffer is full.
*/
public void realWriteBytes(byte cbuf[], int off, int len)
throws IOException;
}
// --------------------
/** Default encoding used to convert to strings. It should be UTF8,
as most standards seem to converge, but the servlet API requires
8859_1, and this object is used mostly for servlets.
*/
public static final Charset DEFAULT_CHARSET = B2CConverter.ISO_8859_1;
// byte[]
private byte[] buff;
private int start=0;
private int end;
private Charset charset;
private boolean isSet=false; // XXX
// How much can it grow, when data is added
private int limit=-1;
private ByteInputChannel in = null;
private ByteOutputChannel out = null;
private boolean optimizedWrite=true;
/**
* Creates a new, uninitialized ByteChunk object.
*/
public ByteChunk() {
// NO-OP
}
public ByteChunk( int initial ) {
allocate( initial, -1 );
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public ByteChunk getClone() {
try {
return (ByteChunk)this.clone();
} catch( Exception ex) {
return null;
}
}
public boolean isNull() {
return ! isSet; // buff==null;
}
/**
* Resets the message buff to an uninitialized state.
*/
public void recycle() {
// buff = null;
charset=null;
start=0;
end=0;
isSet=false;
}
public void reset() {
buff=null;
}
// -------------------- Setup --------------------
public void allocate( int initial, int limit ) {
if( buff==null || buff.length < initial ) {
buff=new byte[initial];
}
this.limit=limit;
start=0;
end=0;
isSet=true;
}
/**
* Sets the message bytes to the specified subarray of bytes.
*
* @param b the ascii bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
buff = b;
start = off;
end = start+ len;
isSet=true;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public void setOptimizedWrite(boolean optimizedWrite) {
this.optimizedWrite = optimizedWrite;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public Charset getCharset() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
return charset;
}
/**
* Returns the message bytes.
*/
public byte[] getBytes() {
return getBuffer();
}
/**
* Returns the message bytes.
*/
public byte[] getBuffer() {
return buff;
}
/**
* Returns the start offset of the bytes.
* For output this is the end of the buffer.
*/
public int getStart() {
return start;
}
public int getOffset() {
return start;
}
public void setOffset(int off) {
if (end < off ) {
end=off;
}
start=off;
}
/**
* Returns the length of the bytes.
* XXX need to clean this up
*/
public int getLength() {
return end-start;
}
/** Maximum amount of data in this buffer.
*
* If -1 or not set, the buffer will grow indefinitely.
* Can be smaller than the current buffer size ( which will not shrink ).
* When the limit is reached, the buffer will be flushed ( if out is set )
* or throw exception.
*/
public void setLimit(int limit) {
this.limit=limit;
}
public int getLimit() {
return limit;
}
/**
* When the buffer is empty, read the data from the input channel.
*/
public void setByteInputChannel(ByteInputChannel in) {
this.in = in;
}
/** When the buffer is full, write the data to the output channel.
* Also used when large amount of data is appended.
*
* If not set, the buffer will grow to the limit.
*/
public void setByteOutputChannel(ByteOutputChannel out) {
this.out=out;
}
public int getEnd() {
return end;
}
public void setEnd( int i ) {
end=i;
}
// -------------------- Adding data to the buffer --------------------
/** Append a char, by casting it to byte. This IS NOT intended for unicode.
*
* @param c
* @throws IOException
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public void append( char c )
throws IOException
{
append( (byte)c);
}
public void append( byte b )
throws IOException
{
makeSpace( 1 );
// couldn't make space
if( limit >0 && end >= limit ) {
flushBuffer();
}
buff[end++]=b;
}
public void append( ByteChunk src )
throws IOException
{
append( src.getBytes(), src.getStart(), src.getLength());
}
/** Add data to the buffer
*/
public void append( byte src[], int off, int len )
throws IOException
{
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// Optimize on a common case.
// If the buffer is empty and the source is going to fill up all the
// space in buffer, may as well write it directly to the output,
// and avoid an extra copy
if ( optimizedWrite && len == limit && end == start && out != null ) {
out.realWriteBytes( src, off, len );
return;
}
// if we have limit and we're below
if( len <= limit - end ) {
// makeSpace will grow the buffer to the limit,
// so we have space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// need more space than we can afford, need to flush
// buffer
// the buffer is already at ( or bigger than ) limit
// We chunk the data into slices fitting in the buffer limit, although
// if the data is written directly if it doesn't fit
int avail=limit-end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
int remain = len - avail;
while (remain > (limit - end)) {
out.realWriteBytes( src, (off + len) - remain, limit - end );
remain = remain - (limit - end);
}
System.arraycopy(src, (off + len) - remain, buff, end, remain);
end += remain;
}
// -------------------- Removing data from the buffer --------------------
public int substract()
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
return (buff[start++] & 0xFF);
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int substract(ByteChunk src)
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
int len = getLength();
src.append(buff, start, len);
start = end;
return len;
}
public byte substractB()
throws IOException {
if ((end - start) == 0) {
if (in == null)
return -1;
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0)
return -1;
}
return (buff[start++]);
}
public int substract( byte src[], int off, int len )
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, src, off, n);
start += n;
return n;
}
/**
* Send the buffer to the sink. Called by append() when the limit is
* reached. You can also call it explicitly to force the data to be written.
*
* @throws IOException
*/
public void flushBuffer()
throws IOException
{
//assert out!=null
if( out==null ) {
throw new IOException( "Buffer overflow, no sink " + limit + " " +
buff.length );
}
out.realWriteBytes( buff, start, end-start );
end=start;
}
/**
* Make space for len chars. If len is small, allocate a reserve space too.
* Never grow bigger than limit.
*/
public void makeSpace(int count) {
byte[] tmp = null;
int newSize;
int desiredSize=end + count;
// Can't grow above the limit
if( limit > 0 &&
desiredSize > limit) {
desiredSize=limit;
}
if( buff==null ) {
if( desiredSize < 256 )
{
desiredSize=256; // take a minimum
}
buff=new byte[desiredSize];
}
// limit < buf.length ( the buffer is already big )
// or we already have space XXX
if( desiredSize <= buff.length ) {
return;
}
// grow in larger chunks
if( desiredSize < 2 * buff.length ) {
newSize= buff.length * 2;
if( limit >0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new byte[newSize];
} else {
newSize= buff.length * 2 + count ;
if( limit > 0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new byte[newSize];
}
System.arraycopy(buff, start, tmp, 0, end-start);
buff = tmp;
tmp = null;
end=end-start;
start=0;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end-start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
// new String(byte[], int, int, Charset) takes a defensive copy of the
// entire byte array. This is expensive if only a small subset of the
// bytes will be used. The code below is from Apache Harmony.
CharBuffer cb;
cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
return new String(cb.array(), cb.arrayOffset(), cb.length());
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int getInt()
{
return Ascii.parseInt(buff, start,end-start);
}
public long getLong() {
return Ascii.parseLong(buff, start,end-start);
}
// -------------------- equals --------------------
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
// XXX ENCODING - this only works if encoding is UTF8-compat
// ( ok for tomcat, where we compare ascii - header names, etc )!!!
byte[] b = buff;
int blen = end-start;
if (b == null || blen != s.length()) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (b[boff++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
byte[] b = buff;
int blen = end-start;
if (b == null || blen != s.length()) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (Ascii.toLower(b[boff++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals( ByteChunk bb ) {
return equals( bb.getBytes(), bb.getStart(), bb.getLength());
}
public boolean equals( byte b2[], int off2, int len2) {
byte b1[]=buff;
if( b1==null && b2==null ) {
return true;
}
int len=end-start;
if ( len2 != len || b1==null || b2==null ) {
return false;
}
int off1 = start;
while ( len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
public boolean equals( CharChunk cc ) {
return equals( cc.getChars(), cc.getStart(), cc.getLength());
}
public boolean equals( char c2[], int off2, int len2) {
// XXX works only for enc compatible with ASCII/UTF !!!
byte b1[]=buff;
if( c2==null && b1==null ) {
return true;
}
if (b1== null || c2==null || end-start != len2 ) {
return false;
}
int off1 = start;
int len=end-start;
while ( len-- > 0) {
if ( (char)b1[off1++] != c2[off2++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public boolean startsWith(String s) {
// Works only if enc==UTF
byte[] b = buff;
int blen = s.length();
if (b == null || blen > end-start) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (b[boff++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes start with the specified byte array.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public boolean startsWith(byte[] b2) {
byte[] b1 = buff;
if (b1 == null && b2 == null) {
return true;
}
int len = end - start;
if (b1 == null || b2 == null || b2.length > len) {
return false;
}
for (int i = start, j = 0; i < end && j < b2.length;) {
if (b1[i++] != b2[j++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @param pos The position
*/
public boolean startsWithIgnoreCase(String s, int pos) {
byte[] b = buff;
int len = s.length();
if (b == null || len+pos > end-start) {
return false;
}
int off = start+pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( b[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
public int indexOf( String src, int srcOff, int srcLen, int myOff ) {
char first=src.charAt( srcOff );
// Look for first char
int srcEnd = srcOff + srcLen;
mainLoop:
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
if( buff[i] != first ) {
continue;
}
// found first char, now look for a match
int myPos=i+1;
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
if( buff[myPos++] != src.charAt( srcPos++ )) {
continue mainLoop;
}
}
return i-start; // found it
}
return -1;
}
// -------------------- Hash code --------------------
// normal hash.
public int hash() {
return hashBytes( buff, start, end-start);
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int hashIgnoreCase() {
return hashBytesIC( buff, start, end-start );
}
private static int hashBytes( byte buff[], int start, int bytesLen ) {
int max=start+bytesLen;
byte bb[]=buff;
int code=0;
for (int i = start; i < max ; i++) {
code = code * 37 + bb[i];
}
return code;
}
private static int hashBytesIC( byte bytes[], int start,
int bytesLen )
{
int max=start+bytesLen;
byte bb[]=bytes;
int code=0;
for (int i = start; i < max ; i++) {
code = code * 37 + Ascii.toLower(bb[i]);
}
return code;
}
/**
* Returns the first instance of the given character in this ByteChunk
* starting at the specified byte. If the character is not found, -1 is
* returned.
* <br/>
* NOTE: This only works for characters in the range 0-127.
*
* @param c The character
* @param starting The start position
* @return The position of the first instance of the character or
* -1 if the character is not found.
*/
public int indexOf(char c, int starting) {
int ret = indexOf(buff, start + starting, end, c);
return (ret >= start) ? ret - start : -1;
}
/**
* Returns the first instance of the given character in the given byte array
* between the specified start and end.
* <br/>
* NOTE: This only works for characters in the range 0-127.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param c The character to search for
* @return The position of the first instance of the character or -1
* if the character is not found.
*/
public static int indexOf(byte bytes[], int start, int end, char c) {
int offset = start;
while (offset < end) {
byte b=bytes[offset];
if (b == c) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of the given byte in the byte array between
* the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The byte to search for
* @return The position of the first instance of the byte or -1 if the
* byte is not found.
*/
public static int findByte(byte bytes[], int start, int end, byte b) {
int offset = start;
while (offset < end) {
if (bytes[offset] == b) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of any of the given bytes in the byte array
* between the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The array of bytes to search for
* @return The position of the first instance of the byte or -1 if the
* byte is not found.
*/
public static int findBytes(byte bytes[], int start, int end, byte b[]) {
int blen = b.length;
int offset = start;
while (offset < end) {
for (int i = 0; i < blen; i++) {
if (bytes[offset] == b[i]) {
return offset;
}
}
offset++;
}
return -1;
}
/**
* Returns the first instance of any byte that is not one of the given bytes
* in the byte array between the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The list of bytes to search for
* @return The position of the first instance a byte that is not
* in the list of bytes to search for or -1 if no such byte
* is found.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int findNotBytes(byte bytes[], int start, int end, byte b[]) {
int blen = b.length;
int offset = start;
boolean found;
while (offset < end) {
found = true;
for (int i = 0; i < blen; i++) {
if (bytes[offset] == b[i]) {
found=false;
break;
}
}
if (found) {
return offset;
}
offset++;
}
return -1;
}
/**
* Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
* chars will be truncated.
*
* @param value to convert to byte array
* @return the byte array value
*/
public static final byte[] convertToBytes(String value) {
byte[] result = new byte[value.length()];
for (int i = 0; i < value.length(); i++) {
result[i] = (byte) value.charAt(i);
}
return result;
}
}
@@ -1,702 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
/**
* Utilities to manipulate char chunks. While String is
* the easiest way to manipulate chars ( search, substrings, etc),
* it is known to not be the most efficient solution - Strings are
* designed as immutable and secure objects.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class CharChunk implements Cloneable, Serializable, CharSequence {
private static final long serialVersionUID = 1L;
// Input interface, used when the buffer is emptied.
public static interface CharInputChannel {
/**
* Read new bytes ( usually the internal conversion buffer ).
* The implementation is allowed to ignore the parameters,
* and mutate the chunk if it wishes to implement its own buffering.
*/
public int realReadChars(char cbuf[], int off, int len)
throws IOException;
}
/**
* When we need more space we'll either
* grow the buffer ( up to the limit ) or send it to a channel.
*/
public static interface CharOutputChannel {
/** Send the bytes ( usually the internal conversion buffer ).
* Expect 8k output if the buffer is full.
*/
public void realWriteChars(char cbuf[], int off, int len)
throws IOException;
}
// --------------------
private int hashCode = 0;
// did we compute the hashcode ?
private boolean hasHashCode = false;
// char[]
private char buff[];
private int start;
private int end;
private boolean isSet=false; // XXX
// -1: grow indefinitely
// maximum amount to be cached
private int limit=-1;
private CharInputChannel in = null;
private CharOutputChannel out = null;
private boolean optimizedWrite=true;
/**
* Creates a new, uninitialized CharChunk object.
*/
public CharChunk() {
}
public CharChunk(int size) {
allocate( size, -1 );
}
// --------------------
public boolean isNull() {
if( end > 0 ) {
return false;
}
return !isSet; //XXX
}
/**
* Resets the message bytes to an uninitialized state.
*/
public void recycle() {
// buff=null;
isSet=false; // XXX
hasHashCode = false;
start=0;
end=0;
}
// -------------------- Setup --------------------
public void allocate( int initial, int limit ) {
if( buff==null || buff.length < initial ) {
buff=new char[initial];
}
this.limit=limit;
start=0;
end=0;
isSet=true;
hasHashCode = false;
}
public void setOptimizedWrite(boolean optimizedWrite) {
this.optimizedWrite = optimizedWrite;
}
public void setChars( char[] c, int off, int len ) {
buff=c;
start=off;
end=start + len;
isSet=true;
hasHashCode = false;
}
/** Maximum amount of data in this buffer.
*
* If -1 or not set, the buffer will grow indefinitely.
* Can be smaller than the current buffer size ( which will not shrink ).
* When the limit is reached, the buffer will be flushed ( if out is set )
* or throw exception.
*/
public void setLimit(int limit) {
this.limit=limit;
}
public int getLimit() {
return limit;
}
/**
* When the buffer is empty, read the data from the input channel.
*/
public void setCharInputChannel(CharInputChannel in) {
this.in = in;
}
/** When the buffer is full, write the data to the output channel.
* Also used when large amount of data is appended.
*
* If not set, the buffer will grow to the limit.
*/
public void setCharOutputChannel(CharOutputChannel out) {
this.out=out;
}
// compat
public char[] getChars()
{
return getBuffer();
}
public char[] getBuffer()
{
return buff;
}
/**
* Returns the start offset of the bytes.
* For output this is the end of the buffer.
*/
public int getStart() {
return start;
}
public int getOffset() {
return start;
}
/**
* Returns the start offset of the bytes.
*/
public void setOffset(int off) {
start=off;
}
/**
* Returns the length of the bytes.
*/
public int getLength() {
return end-start;
}
public int getEnd() {
return end;
}
public void setEnd( int i ) {
end=i;
}
// -------------------- Adding data --------------------
public void append( char b )
throws IOException
{
makeSpace( 1 );
// couldn't make space
if( limit >0 && end >= limit ) {
flushBuffer();
}
buff[end++]=b;
}
public void append( CharChunk src )
throws IOException
{
append( src.getBuffer(), src.getOffset(), src.getLength());
}
/** Add data to the buffer
*/
public void append( char src[], int off, int len )
throws IOException
{
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// Optimize on a common case.
// If the source is going to fill up all the space in buffer, may
// as well write it directly to the output, and avoid an extra copy
if ( optimizedWrite && len == limit && end == start && out != null ) {
out.realWriteChars( src, off, len );
return;
}
// if we have limit and we're below
if( len <= limit - end ) {
// makeSpace will grow the buffer to the limit,
// so we have space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// need more space than we can afford, need to flush
// buffer
// the buffer is already at ( or bigger than ) limit
// Optimization:
// If len-avail < length ( i.e. after we fill the buffer with
// what we can, the remaining will fit in the buffer ) we'll just
// copy the first part, flush, then copy the second part - 1 write
// and still have some space for more. We'll still have 2 writes, but
// we write more on the first.
if( len + end < 2 * limit ) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
We can't avoid 2 writes, but we can write more on the second
*/
int avail=limit-end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
System.arraycopy(src, off+avail, buff, end, len - avail);
end+= len - avail;
} else { // len > buf.length + avail
// long write - flush the buffer and write the rest
// directly from source
flushBuffer();
out.realWriteChars( src, off, len );
}
}
/** Append a string to the buffer
*/
public void append(String s) throws IOException {
append(s, 0, s.length());
}
/** Append a string to the buffer
*/
public void append(String s, int off, int len) throws IOException {
if (s==null) {
return;
}
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
s.getChars(off, off+len, buff, end );
end+=len;
return;
}
int sOff = off;
int sEnd = off + len;
while (sOff < sEnd) {
int d = min(limit - end, sEnd - sOff);
s.getChars( sOff, sOff+d, buff, end);
sOff += d;
end += d;
if (end >= limit) {
flushBuffer();
}
}
}
// -------------------- Removing data from the buffer --------------------
public int substract()
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadChars(buff, end, buff.length - end);
if (n < 0) {
return -1;
}
}
return (buff[start++]);
}
public int substract( char src[], int off, int len )
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadChars( buff, end, buff.length - end);
if (n < 0) {
return -1;
}
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, src, off, n);
start += n;
return n;
}
public void flushBuffer()
throws IOException
{
//assert out!=null
if( out==null ) {
throw new IOException( "Buffer overflow, no sink " + limit + " " +
buff.length );
}
out.realWriteChars( buff, start, end - start );
end=start;
}
/** Make space for len chars. If len is small, allocate
* a reserve space too. Never grow bigger than limit.
*/
public void makeSpace(int count)
{
char[] tmp = null;
int newSize;
int desiredSize=end + count;
// Can't grow above the limit
if( limit > 0 &&
desiredSize > limit) {
desiredSize=limit;
}
if( buff==null ) {
if( desiredSize < 256 )
{
desiredSize=256; // take a minimum
}
buff=new char[desiredSize];
}
// limit < buf.length ( the buffer is already big )
// or we already have space XXX
if( desiredSize <= buff.length) {
return;
}
// grow in larger chunks
if( desiredSize < 2 * buff.length ) {
newSize= buff.length * 2;
if( limit >0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new char[newSize];
} else {
newSize= buff.length * 2 + count ;
if( limit > 0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new char[newSize];
}
System.arraycopy(buff, 0, tmp, 0, end);
buff = tmp;
tmp = null;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end-start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
return new String(buff, start, end-start);
}
// -------------------- equals --------------------
@Override
public boolean equals(Object obj) {
if (obj instanceof CharChunk) {
return equals((CharChunk) obj);
}
return false;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
char[] c = buff;
int len = end-start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
char[] c = buff;
int len = end-start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals(CharChunk cc) {
return equals( cc.getChars(), cc.getOffset(), cc.getLength());
}
public boolean equals(char b2[], int off2, int len2) {
char b1[]=buff;
if( b1==null && b2==null ) {
return true;
}
if (b1== null || b2==null || end-start != len2) {
return false;
}
int off1 = start;
int len=end-start;
while ( len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
*/
public boolean startsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end-start) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
*/
public boolean startsWithIgnoreCase(String s, int pos) {
char[] c = buff;
int len = s.length();
if (c == null || len+pos > end-start) {
return false;
}
int off = start+pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes end with the specified string.
* @param s the string
*/
public boolean endsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end-start) {
return false;
}
int off = end - len;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
// -------------------- Hash code --------------------
@Override
public int hashCode() {
if (hasHashCode) {
return hashCode;
}
int code = 0;
code = hash();
hashCode = code;
hasHashCode = true;
return code;
}
// normal hash.
public int hash() {
int code=0;
for (int i = start; i < start + end-start; i++) {
code = code * 37 + buff[i];
}
return code;
}
public int indexOf(char c) {
return indexOf( c, start);
}
/**
* Returns true if the message bytes starts with the specified string.
* @param c the character
*/
public int indexOf(char c, int starting) {
int ret = indexOf( buff, start+starting, end, c );
return (ret >= start) ? ret - start : -1;
}
public static int indexOf( char chars[], int off, int cend, char qq )
{
while( off < cend ) {
char b=chars[off];
if( b==qq ) {
return off;
}
off++;
}
return -1;
}
public int indexOf(String src, int srcOff, int srcLen, int myOff ) {
char first=src.charAt( srcOff );
// Look for first char
int srcEnd = srcOff + srcLen;
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
if( buff[i] != first ) {
continue;
}
// found first char, now look for a match
int myPos=i+1;
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
if( buff[myPos++] != src.charAt( srcPos++ )) {
break;
}
if( srcPos==srcEnd )
{
return i-start; // found it
}
}
}
return -1;
}
// -------------------- utils
private int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}
// Char sequence impl
public char charAt(int index) {
return buff[index + start];
}
public CharSequence subSequence(int start, int end) {
try {
CharChunk result = (CharChunk) this.clone();
result.setOffset(this.start + start);
result.setEnd(this.start + end);
return result;
} catch (CloneNotSupportedException e) {
// Cannot happen
return null;
}
}
public int length() {
return end - start;
}
}
@@ -1,115 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
/**
* Tables useful when converting byte arrays to and from strings of hexadecimal
* digits.
* Code from Ajp11, from Apache's JServ.
*
* @author Craig R. McClanahan
*/
public final class HexUtils {
// -------------------------------------------------------------- Constants
/**
* Table for HEX to DEC byte translation.
*/
private static final int[] DEC = {
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15,
};
/**
* Table for DEC to HEX byte translation.
*/
private static final byte[] HEX =
{ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
/**
* Table for byte to hex string translation.
*/
private static final char[] hex = "0123456789abcdef".toCharArray();
// --------------------------------------------------------- Static Methods
public static int getDec(int index) {
// Fast for correct values, slower for incorrect ones
try {
return DEC[index - '0'];
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
public static byte getHex(int index) {
return HEX[index];
}
public static String toHexString(byte[] bytes) {
if (null == bytes) {
return null;
}
StringBuilder sb = new StringBuilder(bytes.length << 1);
for(int i = 0; i < bytes.length; ++i) {
sb.append(hex[(bytes[i] & 0xf0) >> 4])
.append(hex[(bytes[i] & 0x0f)])
;
}
return sb.toString();
}
public static byte[] fromHexString(String input) {
if (input == null) {
return null;
}
if ((input.length() & 1) == 1) {
// Odd number of characters
throw new IllegalArgumentException("The input must consist of an even number of hex digits");
}
char[] inputChars = input.toCharArray();
byte[] result = new byte[input.length() >> 1];
for (int i = 0; i < result.length; i++) {
int upperNibble = getDec(inputChars[2*i]);
int lowerNibble = getDec(inputChars[2*i + 1]);
if (upperNibble < 0 || lowerNibble < 0) {
// Non hex character
throw new IllegalArgumentException("The input must consist only of hex digits");
}
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
}
return result;
}
}
@@ -1,548 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* This class is used to represent a subarray of bytes in an HTTP message.
* It represents all request/response elements. The byte/char conversions are
* delayed and cached. Everything is recyclable.
*
* The object can represent a byte[], a char[], or a (sub) String. All
* operations can be made in case sensitive mode or not.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
* @author Costin Manolache
*/
public final class MessageBytes implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
// primary type ( whatever is set as original value )
private int type = T_NULL;
public static final int T_NULL = 0;
/** getType() is T_STR if the the object used to create the MessageBytes
was a String */
public static final int T_STR = 1;
/** getType() is T_STR if the the object used to create the MessageBytes
was a byte[] */
public static final int T_BYTES = 2;
/** getType() is T_STR if the the object used to create the MessageBytes
was a char[] */
public static final int T_CHARS = 3;
private int hashCode=0;
// did we compute the hashcode ?
private boolean hasHashCode=false;
// Internal objects to represent array + offset, and specific methods
private final ByteChunk byteC=new ByteChunk();
private final CharChunk charC=new CharChunk();
// String
private String strValue;
// true if a String value was computed. Probably not needed,
// strValue!=null is the same
private boolean hasStrValue=false;
/**
* Creates a new, uninitialized MessageBytes object.
* Use static newInstance() in order to allow
* future hooks.
*/
private MessageBytes() {
}
/** Construct a new MessageBytes instance
*/
public static MessageBytes newInstance() {
return factory.newInstance();
}
public boolean isNull() {
// should we check also hasStrValue ???
return byteC.isNull() && charC.isNull() && ! hasStrValue;
// bytes==null && strValue==null;
}
/**
* Resets the message bytes to an uninitialized (NULL) state.
*/
public void recycle() {
type=T_NULL;
byteC.recycle();
charC.recycle();
strValue=null;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to the specified subarray of bytes.
*
* @param b the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
byteC.setBytes( b, off, len );
type=T_BYTES;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to be a char[]
*
* @param c the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setChars( char[] c, int off, int len ) {
charC.setChars( c, off, len );
type=T_CHARS;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Set the content to be a string
*/
public void setString( String s ) {
strValue=s;
hasHashCode=false;
hasLongValue=false;
if (s == null) {
hasStrValue=false;
type=T_NULL;
} else {
hasStrValue=true;
type=T_STR;
}
}
// -------------------- Conversion and getters --------------------
/** Compute the string value
*/
@Override
public String toString() {
if( hasStrValue ) {
return strValue;
}
switch (type) {
case T_CHARS:
strValue=charC.toString();
hasStrValue=true;
return strValue;
case T_BYTES:
strValue=byteC.toString();
hasStrValue=true;
return strValue;
}
return null;
}
//----------------------------------------
/** Return the type of the original content. Can be
* T_STR, T_BYTES, T_CHARS or T_NULL
*/
public int getType() {
return type;
}
/**
* Returns the byte chunk, representing the byte[] and offset/length.
* Valid only if T_BYTES or after a conversion was made.
*/
public ByteChunk getByteChunk() {
return byteC;
}
/**
* Returns the char chunk, representing the char[] and offset/length.
* Valid only if T_CHARS or after a conversion was made.
*/
public CharChunk getCharChunk() {
return charC;
}
/**
* Returns the string value.
* Valid only if T_STR or after a conversion was made.
*/
public String getString() {
return strValue;
}
/**
* Get the Charset used for string&lt;-&gt;byte conversions.
*/
public Charset getCharset() {
return byteC.getCharset();
}
/**
* Set the Charset used for string&lt;-&gt;byte conversions.
*/
public void setCharset(Charset charset) {
byteC.setCharset(charset);
}
/** Do a char-&gt;byte conversion.
*/
public void toBytes() {
if (!byteC.isNull()) {
type=T_BYTES;
return;
}
toString();
type=T_BYTES;
Charset charset = byteC.getCharset();
ByteBuffer result = charset.encode(strValue);
byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
}
/** Convert to char[] and fill the CharChunk.
* XXX Not optimized - it converts to String first.
*/
public void toChars() {
if( ! charC.isNull() ) {
type=T_CHARS;
return;
}
// inefficient
toString();
type=T_CHARS;
char cc[]=strValue.toCharArray();
charC.setChars(cc, 0, cc.length);
}
/**
* Returns the length of the original buffer.
* Note that the length in bytes may be different from the length
* in chars.
*/
public int getLength() {
if(type==T_BYTES) {
return byteC.getLength();
}
if(type==T_CHARS) {
return charC.getLength();
}
if(type==T_STR) {
return strValue.length();
}
toString();
if( strValue==null ) {
return 0;
}
return strValue.length();
}
// -------------------- equals --------------------
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equals( s );
case T_CHARS:
return charC.equals( s );
case T_BYTES:
return byteC.equals( s );
default:
return false;
}
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equalsIgnoreCase( s );
case T_CHARS:
return charC.equalsIgnoreCase( s );
case T_BYTES:
return byteC.equalsIgnoreCase( s );
default:
return false;
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MessageBytes) {
return equals((MessageBytes) obj);
}
return false;
}
public boolean equals(MessageBytes mb) {
switch (type) {
case T_STR:
return mb.equals( strValue );
}
if( mb.type != T_CHARS &&
mb.type!= T_BYTES ) {
// it's a string or int/date string value
return equals( mb.toString() );
}
// mb is either CHARS or BYTES.
// this is either CHARS or BYTES
// Deal with the 4 cases ( in fact 3, one is symmetric)
if( mb.type == T_CHARS && type==T_CHARS ) {
return charC.equals( mb.charC );
}
if( mb.type==T_BYTES && type== T_BYTES ) {
return byteC.equals( mb.byteC );
}
if( mb.type== T_CHARS && type== T_BYTES ) {
return byteC.equals( mb.charC );
}
if( mb.type== T_BYTES && type== T_CHARS ) {
return mb.byteC.equals( charC );
}
// can't happen
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @param pos The start position
*/
public boolean startsWithIgnoreCase(String s, int pos) {
switch (type) {
case T_STR:
if( strValue==null ) {
return false;
}
if( strValue.length() < pos + s.length() ) {
return false;
}
for( int i=0; i<s.length(); i++ ) {
if( Ascii.toLower( s.charAt( i ) ) !=
Ascii.toLower( strValue.charAt( pos + i ))) {
return false;
}
}
return true;
case T_CHARS:
return charC.startsWithIgnoreCase( s, pos );
case T_BYTES:
return byteC.startsWithIgnoreCase( s, pos );
default:
return false;
}
}
// -------------------- Hash code --------------------
@Override
public int hashCode() {
if( hasHashCode ) {
return hashCode;
}
int code = 0;
code=hash();
hashCode=code;
hasHashCode=true;
return code;
}
// normal hash.
private int hash() {
int code=0;
switch (type) {
case T_STR:
// We need to use the same hash function
for (int i = 0; i < strValue.length(); i++) {
code = code * 37 + strValue.charAt( i );
}
return code;
case T_CHARS:
return charC.hash();
case T_BYTES:
return byteC.hash();
default:
return 0;
}
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s, int starting) {
toString();
return strValue.indexOf( s, starting );
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s) {
return indexOf( s, 0 );
}
public int indexOfIgnoreCase(String s, int starting) {
toString();
String upper=strValue.toUpperCase(Locale.ENGLISH);
String sU=s.toUpperCase(Locale.ENGLISH);
return upper.indexOf( sU, starting );
}
/** Copy the src into this MessageBytes, allocating more space if
* needed
*/
public void duplicate( MessageBytes src ) throws IOException
{
switch( src.getType() ) {
case MessageBytes.T_BYTES:
type=T_BYTES;
ByteChunk bc=src.getByteChunk();
byteC.allocate( 2 * bc.getLength(), -1 );
byteC.append( bc );
break;
case MessageBytes.T_CHARS:
type=T_CHARS;
CharChunk cc=src.getCharChunk();
charC.allocate( 2 * cc.getLength(), -1 );
charC.append( cc );
break;
case MessageBytes.T_STR:
type=T_STR;
String sc=src.getString();
this.setString( sc );
break;
}
}
// -------------------- Deprecated code --------------------
// efficient long
// XXX used only for headers - shouldn't be stored here.
private long longValue;
private boolean hasLongValue=false;
/** Set the buffer to the representation of an long
*/
public void setLong(long l) {
byteC.allocate(32, 64);
long current = l;
byte[] buf = byteC.getBuffer();
int start = 0;
int end = 0;
if (l == 0) {
buf[end++] = (byte) '0';
}
if (l < 0) {
current = -l;
buf[end++] = (byte) '-';
}
while (current > 0) {
int digit = (int) (current % 10);
current = current / 10;
buf[end++] = HexUtils.getHex(digit);
}
byteC.setOffset(0);
byteC.setEnd(end);
// Inverting buffer
end--;
if (l < 0) {
start++;
}
while (end > start) {
byte temp = buf[start];
buf[start] = buf[end];
buf[end] = temp;
start++;
end--;
}
longValue=l;
hasStrValue=false;
hasHashCode=false;
hasLongValue=true;
type=T_BYTES;
}
// Used for headers conversion
/** Convert the buffer to an long, cache the value
*/
public long getLong() {
if( hasLongValue ) {
return longValue;
}
switch (type) {
case T_BYTES:
longValue=byteC.getLong();
break;
default:
longValue= Long.parseLong(toString());
}
hasLongValue=true;
return longValue;
}
// -------------------- Future may be different --------------------
private static final MessageBytesFactory factory=new MessageBytesFactory();
private static class MessageBytesFactory {
protected MessageBytesFactory() {
}
public MessageBytes newInstance() {
return new MessageBytes();
}
}
}
@@ -1,697 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
* This class implements a String cache for ByteChunk and CharChunk.
*
* @author Remy Maucherat
*/
public class StringCache {
private static final Logger log = LogManager.getLogger(StringCache.class);
// ------------------------------------------------------- Static Variables
/**
* Enabled ?
*/
protected static boolean byteEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.byte.enabled", "false")));
protected static boolean charEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.char.enabled", "false")));
protected static int trainThreshold = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
protected static int cacheSize = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.cacheSize", "200"));
protected static final int maxStringSize =
Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.maxStringSize", "128"));
/**
* Statistics hash map for byte chunk.
*/
protected static final HashMap<ByteEntry,int[]> bcStats =
new HashMap<ByteEntry, int[]>(cacheSize);
/**
* toString count for byte chunk.
*/
protected static int bcCount = 0;
/**
* Cache for byte chunk.
*/
protected static ByteEntry[] bcCache = null;
/**
* Statistics hash map for char chunk.
*/
protected static final HashMap<CharEntry,int[]> ccStats =
new HashMap<CharEntry, int[]>(cacheSize);
/**
* toString count for char chunk.
*/
protected static int ccCount = 0;
/**
* Cache for char chunk.
*/
protected static CharEntry[] ccCache = null;
/**
* Access count.
*/
protected static int accessCount = 0;
/**
* Hit count.
*/
protected static int hitCount = 0;
// ------------------------------------------------------------ Properties
/**
* @return Returns the cacheSize.
*/
public int getCacheSize() {
return cacheSize;
}
/**
* @param cacheSize The cacheSize to set.
*/
public void setCacheSize(int cacheSize) {
StringCache.cacheSize = cacheSize;
}
/**
* @return Returns the enabled.
*/
public boolean getByteEnabled() {
return byteEnabled;
}
/**
* @param byteEnabled The enabled to set.
*/
public void setByteEnabled(boolean byteEnabled) {
StringCache.byteEnabled = byteEnabled;
}
/**
* @return Returns the enabled.
*/
public boolean getCharEnabled() {
return charEnabled;
}
/**
* @param charEnabled The enabled to set.
*/
public void setCharEnabled(boolean charEnabled) {
StringCache.charEnabled = charEnabled;
}
/**
* @return Returns the trainThreshold.
*/
public int getTrainThreshold() {
return trainThreshold;
}
/**
* @param trainThreshold The trainThreshold to set.
*/
public void setTrainThreshold(int trainThreshold) {
StringCache.trainThreshold = trainThreshold;
}
/**
* @return Returns the accessCount.
*/
public int getAccessCount() {
return accessCount;
}
/**
* @return Returns the hitCount.
*/
public int getHitCount() {
return hitCount;
}
// -------------------------------------------------- Public Static Methods
public void reset() {
hitCount = 0;
accessCount = 0;
synchronized (bcStats) {
bcCache = null;
bcCount = 0;
}
synchronized (ccStats) {
ccCache = null;
ccCount = 0;
}
}
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (bcCache == null) {
String value = bc.toStringInternal();
if (byteEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (bcStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (bcCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (bcCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
new TreeMap<Integer, ArrayList<ByteEntry>>();
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
ByteEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<ByteEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<ByteEntry>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = bcStats.size();
if (size > cacheSize) {
size = cacheSize;
}
ByteEntry[] tempbcCache = new ByteEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
ByteChunk tempChunk = new ByteChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<ByteEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
ByteEntry entry = list.get(i);
tempChunk.setBytes(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempbcCache, n);
if (insertPos == n) {
tempbcCache[n + 1] = entry;
} else {
System.arraycopy(tempbcCache, insertPos + 1,
tempbcCache, insertPos + 2,
n - insertPos - 1);
tempbcCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
bcCount = 0;
bcStats.clear();
bcCache = tempbcCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("ByteCache generation time: " +
(t2 - t1) + "ms");
}
} else {
bcCount++;
// Allocate new ByteEntry for the lookup
ByteEntry entry = new ByteEntry();
entry.value = value;
int[] count = bcStats.get(entry);
if (count == null) {
int end = bc.getEnd();
int start = bc.getStart();
// Create byte array and copy bytes
entry.name = new byte[bc.getLength()];
System.arraycopy(bc.getBuffer(), start, entry.name,
0, end - start);
// Set encoding
entry.charset = bc.getCharset();
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
bcStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(bc);
if (result == null) {
return bc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
public static String toString(CharChunk cc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (ccCache == null) {
String value = cc.toStringInternal();
if (charEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (ccStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (ccCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (ccCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<CharEntry>> tempMap =
new TreeMap<Integer, ArrayList<CharEntry>>();
for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
CharEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<CharEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<CharEntry>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = ccStats.size();
if (size > cacheSize) {
size = cacheSize;
}
CharEntry[] tempccCache = new CharEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
CharChunk tempChunk = new CharChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<CharEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
CharEntry entry = list.get(i);
tempChunk.setChars(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempccCache, n);
if (insertPos == n) {
tempccCache[n + 1] = entry;
} else {
System.arraycopy(tempccCache, insertPos + 1,
tempccCache, insertPos + 2,
n - insertPos - 1);
tempccCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
ccCount = 0;
ccStats.clear();
ccCache = tempccCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("CharCache generation time: " +
(t2 - t1) + "ms");
}
} else {
ccCount++;
// Allocate new CharEntry for the lookup
CharEntry entry = new CharEntry();
entry.value = value;
int[] count = ccStats.get(entry);
if (count == null) {
int end = cc.getEnd();
int start = cc.getStart();
// Create char array and copy chars
entry.name = new char[cc.getLength()];
System.arraycopy(cc.getBuffer(), start, entry.name,
0, end - start);
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
ccStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(cc);
if (result == null) {
return cc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
// ----------------------------------------------------- Protected Methods
/**
* Compare given byte chunk with byte array.
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(ByteChunk name, byte[] compareTo) {
int result = 0;
byte[] b = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (b[i + start] > compareTo[i]) {
result = 1;
} else if (b[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
*/
protected static final String find(ByteChunk name) {
int pos = findClosest(name, bcCache, bcCache.length);
if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
|| !(name.getCharset().equals(bcCache[pos].charset))) {
return null;
} else {
return bcCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
*/
protected static final int findClosest(ByteChunk name, ByteEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
/**
* Compare given char chunk with char array.
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(CharChunk name, char[] compareTo) {
int result = 0;
char[] c = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (c[i + start] > compareTo[i]) {
result = 1;
} else if (c[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
*/
protected static final String find(CharChunk name) {
int pos = findClosest(name, ccCache, ccCache.length);
if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
return null;
} else {
return ccCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
*/
protected static final int findClosest(CharChunk name, CharEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0 ) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
// -------------------------------------------------- ByteEntry Inner Class
private static class ByteEntry {
private byte[] name = null;
private Charset charset = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ByteEntry) {
return value.equals(((ByteEntry) obj).value);
}
return false;
}
}
// -------------------------------------------------- CharEntry Inner Class
private static class CharEntry {
private char[] name = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CharEntry) {
return value.equals(((CharEntry) obj).value);
}
return false;
}
}
}
@@ -1,423 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* All URL decoding happens here. This way we can reuse, review, optimize
* without adding complexity to the buffers.
*
* The conversion will modify the original buffer.
*
* @author Costin Manolache
*/
public final class UDecoder {
private static final Logger log = LogManager.getLogger(UDecoder.class);
public static final boolean ALLOW_ENCODED_SLASH =
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"));
private static class DecodeException extends CharConversionException {
private static final long serialVersionUID = 1L;
public DecodeException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
// This class does not provide a stack trace
return this;
}
}
/** Unexpected end of data. */
private static final IOException EXCEPTION_EOF = new DecodeException("EOF");
/** %xx with not-hex digit */
private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
"isHexDigit");
/** %-encoded slash is forbidden in resource path */
private static final IOException EXCEPTION_SLASH = new DecodeException(
"noSlash");
public UDecoder()
{
}
/** URLDecode, will modify the source.
*/
public void convert( ByteChunk mb, boolean query )
throws IOException
{
int start=mb.getOffset();
byte buff[]=mb.getBytes();
int end=mb.getEnd();
int idx= ByteChunk.findByte( buff, start, end, (byte) '%' );
int idx2=-1;
if( query ) {
idx2= ByteChunk.findByte( buff, start, (idx >= 0 ? idx : end), (byte) '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<end; j++, idx++ ) {
if( buff[ j ] == '+' && query) {
buff[idx]= (byte)' ' ;
} else if( buff[ j ] != '%' ) {
buff[idx]= buff[j];
} else {
// read next 2 digits
if( j+2 >= end ) {
throw EXCEPTION_EOF;
}
byte b1= buff[j+1];
byte b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(byte)res;
}
}
mb.setEnd( idx );
return;
}
// -------------------- Additional methods --------------------
// XXX What do we do about charset ????
/** In-buffer processing - the buffer will be modified
*/
public void convert( CharChunk mb, boolean query )
throws IOException
{
// log( "Converting a char chunk ");
int start=mb.getOffset();
char buff[]=mb.getBuffer();
int cend=mb.getEnd();
int idx= CharChunk.indexOf( buff, start, cend, '%' );
int idx2=-1;
if( query ) {
idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<cend; j++, idx++ ) {
if( buff[ j ] == '+' && query ) {
buff[idx]=( ' ' );
} else if( buff[ j ] != '%' ) {
buff[idx]=buff[j];
} else {
// read next 2 digits
if( j+2 >= cend ) {
// invalid
throw EXCEPTION_EOF;
}
char b1= buff[j+1];
char b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(char)res;
}
}
mb.setEnd( idx );
}
/** URLDecode, will modify the source
*/
public void convert(MessageBytes mb, boolean query)
throws IOException
{
switch (mb.getType()) {
case MessageBytes.T_STR:
String strValue=mb.toString();
if( strValue==null ) {
return;
}
try {
mb.setString( convert( strValue, query ));
} catch (RuntimeException ex) {
throw new DecodeException(ex.getMessage());
}
break;
case MessageBytes.T_CHARS:
CharChunk charC=mb.getCharChunk();
convert( charC, query );
break;
case MessageBytes.T_BYTES:
ByteChunk bytesC=mb.getByteChunk();
convert( bytesC, query );
break;
}
}
// XXX Old code, needs to be replaced !!!!
//
public final String convert(String str, boolean query)
{
if (str == null) {
return null;
}
if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
return str;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
StringBuilder dec = new StringBuilder(); // decoded string output
int strPos = 0;
int strLen = str.length();
dec.ensureCapacity(str.length());
while (strPos < strLen) {
int laPos; // lookahead position
// look ahead to next URLencoded metacharacter, if any
for (laPos = strPos; laPos < strLen; laPos++) {
char laChar = str.charAt(laPos);
if ((laChar == '+' && query) || (laChar == '%')) {
break;
}
}
// if there were non-metacharacters, copy them all as a block
if (laPos > strPos) {
dec.append(str.substring(strPos,laPos));
strPos = laPos;
}
// shortcut out of here if we're at the end of the string
if (strPos >= strLen) {
break;
}
// process next metacharacter
char metaChar = str.charAt(strPos);
if (metaChar == '+') {
dec.append(' ');
strPos++;
continue;
} else if (metaChar == '%') {
// We throw the original exception - the super will deal with
// it
// try {
char res = (char) Integer.parseInt(
str.substring(strPos + 1, strPos + 3), 16);
if (noSlash && (res == '/')) {
throw new IllegalArgumentException("noSlash");
}
dec.append(res);
strPos += 3;
}
}
return dec.toString();
}
/**
* Decode and return the specified URL-encoded String.
* When the byte array is converted to a string, the system default
* character encoding is used... This may be different than some other
* servers. It is assumed the string is not a query string.
*
* @param str The url-encoded string
*
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str) {
return URLDecode(str, null);
}
/**
* Decode and return the specified URL-encoded String. It is assumed the
* string is not a query string.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, String enc) {
return URLDecode(str, enc, false);
}
/**
* Decode and return the specified URL-encoded String.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, String enc, boolean isQuery) {
if (str == null)
return (null);
// use the specified encoding to extract bytes out of the
// given string so that the encoding is not lost. If an
// encoding is not specified, use ISO-8859-1
byte[] bytes = null;
try {
if (enc == null) {
bytes = str.getBytes("ISO-8859-1");
} else {
bytes = str.getBytes(B2CConverter.getCharset(enc));
}
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug("Unable to URL decode the specified input since the encoding "+ enc + " is not supported.", uee);
}
}
return URLDecode(bytes, enc, isQuery);
}
/**
* Decode and return the specified URL-encoded byte array.
*
* @param bytes The url-encoded byte array
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
if (bytes == null)
return null;
int len = bytes.length;
int ix = 0;
int ox = 0;
while (ix < len) {
byte b = bytes[ix++]; // Get byte to test
if (b == '+' && isQuery) {
b = (byte)' ';
} else if (b == '%') {
if (ix + 2 > len) {
throw new IllegalArgumentException(
"The % character must be followed by two hexademical digits");
}
b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
+ convertHexDigit(bytes[ix++]));
}
bytes[ox++] = b;
}
if (enc != null) {
try {
return new String(bytes, 0, ox, B2CConverter.getCharset(enc));
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug("Unable to URL decode the specified input since the encoding " + enc + " is not supported.", uee);
}
return null;
}
}
return new String(bytes, 0, ox);
}
private static byte convertHexDigit( byte b ) {
if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit");
}
private static boolean isHexDigit( int c ) {
return ( ( c>='0' && c<='9' ) ||
( c>='a' && c<='f' ) ||
( c>='A' && c<='F' ));
}
private static int x2c( byte b1, byte b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
private static int x2c( char b1, char b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
}
@@ -1,295 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.util.tomcat.buf;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
/**
* Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
* code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
* does not reject these. It has also been modified to reject code points
* greater than U+10FFFF which the standard Java decoder rejects but the harmony
* one does not.
*/
public class Utf8Decoder extends CharsetDecoder {
// The next table contains information about UTF-8 charset and
// correspondence of 1st byte to the length of sequence
// For information please visit http://www.ietf.org/rfc/rfc3629.txt
//
// Please note, o means 0, actually.
// -------------------------------------------------------------------
// 0 1 2 3 Value
// -------------------------------------------------------------------
// oxxxxxxx 00000000 00000000 0xxxxxxx
// 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
// 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
// 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
private static final int remainingBytes[] = {
// 1owwwwww
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// 11oyyyyy
-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// 111ozzzz
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// 1111ouuu
3, 3, 3, 3, 3, -1, -1, -1,
// > 11110111
-1, -1, -1, -1, -1, -1, -1, -1};
private static final int remainingNumbers[] = {0, // 0 1 2 3
4224, // (01o00000b << 6)+(1o000000b)
401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b <<
// 6)+(1o000000b)
};
private static final int lowerEncodingLimit[] = {-1, 0x80, 0x800, 0x10000};
public Utf8Decoder() {
super(B2CConverter.UTF_8, 1.0f, 1.0f);
}
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
if (in.hasArray() && out.hasArray()) {
return decodeHasArray(in, out);
}
return decodeNotHasArray(in, out);
}
private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
try {
while (pos < limit) {
if (outRemaining == 0) {
return CoderResult.OVERFLOW;
}
int jchar = in.get();
if (jchar < 0) {
jchar = jchar & 0x7F;
int tail = remainingBytes[jchar];
if (tail == -1) {
return CoderResult.malformedForLength(1);
}
if (limit - pos < 1 + tail) {
// No early test for invalid sequences here as peeking
// at the next byte is harder
return CoderResult.UNDERFLOW;
}
int nextByte;
for (int i = 0; i < tail; i++) {
nextByte = in.get() & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in a fewer octets
return CoderResult.malformedForLength(1);
}
pos += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
out.put((char) jchar);
outRemaining--;
} else {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
out.put((char) ((jchar >> 0xA) + 0xD7C0));
out.put((char) ((jchar & 0x3FF) + 0xDC00));
outRemaining -= 2;
}
pos++;
}
return CoderResult.UNDERFLOW;
} finally {
in.position(pos);
}
}
private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
final byte[] bArr = in.array();
final char[] cArr = out.array();
final int inIndexLimit = limit + in.arrayOffset();
int inIndex = pos + in.arrayOffset();
int outIndex = out.position() + out.arrayOffset();
// if someone would change the limit in process,
// he would face consequences
for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
int jchar = bArr[inIndex];
if (jchar < 0) {
jchar = jchar & 0x7F;
// If first byte is invalid, tail will be set to -1
int tail = remainingBytes[jchar];
if (tail == -1) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// Additional checks to detect invalid sequences ASAP
// Checks derived from Unicode 6.2, Chapter 3, Table 3-7
// Check 2nd byte
int tailAvailable = inIndexLimit - inIndex - 1;
if (tailAvailable > 0) {
// First byte C2..DF, second byte 80..BF
if (jchar > 0x41 && jchar < 0x60 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E0, second byte A0..BF
if (jchar == 0x60 && (bArr[inIndex + 1] & 0xE0) != 0xA0) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E1..EC, second byte 80..BF
if (jchar > 0x60 && jchar < 0x6D &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte ED, second byte 80..9F
if (jchar == 0x6D && (bArr[inIndex + 1] & 0xE0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte EE..EF, second byte 80..BF
if (jchar > 0x6D && jchar < 0x70 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F0, second byte 90..BF
if (jchar == 0x70 &&
((bArr[inIndex + 1] & 0xFF) < 0x90 ||
(bArr[inIndex + 1] & 0xFF) > 0xBF)) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F1..F3, second byte 80..BF
if (jchar > 0x70 && jchar < 0x74 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F4, second byte 80..8F
if (jchar == 0x74 &&
(bArr[inIndex + 1] & 0xF0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
}
// Check third byte if present and expected
if (tailAvailable > 1 && tail > 1) {
if ((bArr[inIndex + 2] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(2);
}
}
// Check fourth byte if present and expected
if (tailAvailable > 2 && tail > 2) {
if ((bArr[inIndex + 3] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(3);
}
}
if (tailAvailable < tail) {
break;
}
for (int i = 0; i < tail; i++) {
int nextByte = bArr[inIndex + i + 1] & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in fewer octets
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
inIndex += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
cArr[outIndex++] = (char) jchar;
outRemaining--;
} else {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
outRemaining -= 2;
}
}
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return (outRemaining == 0 && inIndex < inIndexLimit) ?
CoderResult.OVERFLOW :
CoderResult.UNDERFLOW;
}
}
@@ -18,8 +18,17 @@
*/
package org.apache.struts2.views.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import com.opensymphony.xwork2.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.url.UrlDecoder;
import org.apache.struts2.url.UrlEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@@ -27,18 +36,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.util.URLDecoderUtil;
import com.opensymphony.xwork2.inject.Inject;
/**
* Default implementation of UrlHelper
*/
@@ -49,16 +46,11 @@ public class DefaultUrlHelper implements UrlHelper {
public static final String HTTP_PROTOCOL = "http";
public static final String HTTPS_PROTOCOL = "https";
private String encoding = "UTF-8";
private int httpPort = DEFAULT_HTTP_PORT;
private int httpsPort = DEFAULT_HTTPS_PORT;
@Inject(StrutsConstants.STRUTS_I18N_ENCODING)
public void setEncoding(String encoding) {
if (StringUtils.isNotEmpty(encoding)) {
this.encoding = encoding;
}
}
private UrlEncoder encoder;
private UrlDecoder decoder;
@Inject(StrutsConstants.STRUTS_URL_HTTP_PORT)
public void setHttpPort(String httpPort) {
@@ -70,6 +62,16 @@ public class DefaultUrlHelper implements UrlHelper {
this.httpsPort = Integer.parseInt(httpsPort);
}
@Inject
public void setEncoder(UrlEncoder encoder) {
this.encoder = encoder;
}
@Inject
public void setDecoder(UrlDecoder decoder) {
this.decoder = decoder;
}
public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) {
return buildUrl(action, request, response, params, null, true, true);
}
@@ -81,7 +83,7 @@ public class DefaultUrlHelper implements UrlHelper {
public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, String scheme,
boolean includeContext, boolean encodeResult, boolean forceAddSchemeHostAndPort) {
return buildUrl(action, request, response, params, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, true);
return buildUrl(action, request, response, params, scheme, includeContext, encodeResult, forceAddSchemeHostAndPort, true);
}
public String buildUrl(String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, String urlScheme,
@@ -108,7 +110,7 @@ public class DefaultUrlHelper implements UrlHelper {
// If switching schemes, use the configured port for the particular scheme.
if (!scheme.equals(reqScheme)) {
appendPort(link, scheme, HTTP_PROTOCOL.equals(scheme) ? httpPort : httpsPort);
// Else use the port from the current request.
// Else use the port from the current request.
} else {
appendPort(link, scheme, request.getServerPort());
}
@@ -143,7 +145,7 @@ public class DefaultUrlHelper implements UrlHelper {
uri = request.getRequestURI();
}
link.append(uri.substring(0, uri.lastIndexOf('/') + 1));
link.append(uri, 0, uri.lastIndexOf('/') + 1);
}
// Add page
@@ -175,7 +177,7 @@ public class DefaultUrlHelper implements UrlHelper {
String result = link.toString();
if (StringUtils.containsIgnoreCase(result, "<script")){
if (StringUtils.containsIgnoreCase(result, "<script")) {
result = StringEscapeUtils.escapeEcmaScript(result);
}
try {
@@ -215,7 +217,7 @@ public class DefaultUrlHelper implements UrlHelper {
Object value = entry.getValue();
if (value instanceof Iterable) {
for (Iterator iterator = ((Iterable) value).iterator(); iterator.hasNext();) {
for (Iterator<?> iterator = ((Iterable<?>) value).iterator(); iterator.hasNext(); ) {
Object paramValue = iterator.next();
link.append(buildParameterSubstring(name, paramValue != null ? paramValue.toString() : StringUtils.EMPTY, encode));
@@ -249,56 +251,50 @@ public class DefaultUrlHelper implements UrlHelper {
}
private String buildParameterSubstring(String name, String value, boolean encode) {
StringBuilder builder = new StringBuilder();
builder.append(encode ? encode(name) : name);
builder.append('=');
builder.append(encode ? encode(value) : value);
return builder.toString();
String encodedName = encode ? encoder.encode(name) : name;
String encodedValue = encode ? encoder.encode(value) : value;
return encodedName + '=' + encodedValue;
}
/**
* Encodes the URL using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration.
*
* @param input the input to encode
* @return the encoded string
*/
public String encode( String input ) {
try {
return URLEncoder.encode(input, encoding);
} catch (UnsupportedEncodingException e) {
LOG.warn("Could not encode URL parameter '{}', returning value un-encoded", input);
return input;
}
}
/**
* Decodes the URL using {@link URLDecoderUtil#decode(String, String)} with the encoding specified in the configuration.
*
* @param input the input to decode
* @return the encoded string
*/
public String decode( String input ) {
return URLDecoderUtil.decode(input, encoding, false);
}
/**
* Encodes the URL using {@link UrlEncoder#encode} with the encoding specified in the configuration.
*
* @param input the input to encode
* @return the encoded string
* @deprecated since 6.1.0, use {@link UrlEncoder} directly, use {@link Inject} to inject a proper instance
*/
@Deprecated
public String encode(String input) {
return encoder.encode(input);
}
/**
* Decodes the URL using {@link URLDecoderUtil#decode(String, String, boolean)} with the encoding specified in the configuration.
* Decodes the URL using {@link UrlDecoder#decode(String, boolean)} with the encoding specified in the configuration.
*
* @param input the input to decode
* @return the encoded string
* @deprecated since 6.1.0, use {@link UrlDecoder} directly, use {@link Inject} to inject a proper instance
*/
@Deprecated
public String decode(String input) {
return decoder.decode(input, false);
}
/**
* Decodes the URL using {@link UrlDecoder#decode(String, boolean)} with the encoding specified in the configuration.
*
* @param input the input to decode
* @param isQueryString whether input is a query string. If <code>true</code> other decoding rules apply.
* @return the encoded string
* @deprecated since 6.1.0, use {@link UrlDecoder} directly, use {@link Inject} to inject a proper instance
*/
public String decode( String input, boolean isQueryString ) {
try {
return URLDecoderUtil.decode(input, encoding, isQueryString);
} catch (Exception e) {
LOG.warn("Could not decode URL parameter '{}', returning value un-decoded", input);
return input;
}
@Deprecated
public String decode(String input, boolean isQueryString) {
return decoder.decode(input, isQueryString);
}
public Map<String, Object> parseQueryString(String queryString, boolean forceValueArray) {
Map<String, Object> queryParams = new LinkedHashMap<String, Object>();
Map<String, Object> queryParams = new LinkedHashMap<>();
if (queryString != null) {
String[] params = queryString.split("&");
for (String param : params) {
@@ -313,8 +309,8 @@ public class DefaultUrlHelper implements UrlHelper {
paramValue = tmpParams[1];
}
if (paramName != null) {
paramName = decode(paramName, true);
String translatedParamValue = decode(paramValue, true);
paramName = decoder.decode(paramName, true);
String translatedParamValue = decoder.decode(paramValue, true);
if (queryParams.containsKey(paramName) || forceValueArray) {
// WW-1619 append new param value to existing value(s)
@@ -322,11 +318,11 @@ public class DefaultUrlHelper implements UrlHelper {
if (currentParam instanceof String) {
queryParams.put(paramName, new String[]{(String) currentParam, translatedParamValue});
} else {
String currentParamValues[] = (String[]) currentParam;
String[] currentParamValues = (String[]) currentParam;
if (currentParamValues != null) {
List<String> paramList = new ArrayList<String>(Arrays.asList(currentParamValues));
List<String> paramList = new ArrayList<>(Arrays.asList(currentParamValues));
paramList.add(translatedParamValue);
queryParams.put(paramName, paramList.toArray(new String[paramList.size()]));
queryParams.put(paramName, paramList.toArray(new String[0]));
} else {
queryParams.put(paramName, new String[]{translatedParamValue});
}
@@ -279,4 +279,7 @@ struts.ognl.expressionMaxLength=256
### These formatters are using a slightly different patterns, please check JavaDocs of both and more details is in WW-5016
struts.date.formatter=dateTimeFormatter
struts.url.encoder=strutsUrlEncoder
struts.url.decoder=strutsUrlDecoder
### END SNIPPET: complete_file
@@ -313,6 +313,11 @@
<bean type="com.opensymphony.xwork2.ognl.BeanInfoCacheFactory" name="struts"
class="com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory" scope="singleton"/>
<bean type="org.apache.struts2.url.UrlEncoder" name="strutsUrlEncoder"
class="org.apache.struts2.url.StrutsUrlEncoder" scope="singleton"/>
<bean type="org.apache.struts2.url.UrlDecoder" name="strutsUrlDecoder"
class="org.apache.struts2.url.StrutsUrlDecoder" scope="singleton"/>
<package name="struts-default" abstract="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
@@ -25,6 +25,7 @@ import com.opensymphony.xwork2.config.ConfigurationManager;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.impl.DefaultConfiguration;
import org.apache.struts2.url.StrutsUrlDecoder;
import java.util.HashMap;
@@ -40,6 +41,7 @@ public class Restful2ActionMapperTest extends StrutsInternalTestCase {
super.setUp();
mapper = new Restful2ActionMapper();
mapper.setExtensions("");
mapper.setDecoder(new StrutsUrlDecoder());
req = new MockHttpServletRequest();
req.setupGetParameterMap(new HashMap());
req.setupGetContextPath("/my/namespace");
@@ -57,7 +59,7 @@ public class Restful2ActionMapperTest extends StrutsInternalTestCase {
}
};
}
public void testGetIndex() throws Exception {
req.setupGetRequestURI("/my/namespace/foo/");
req.setupGetServletPath("/my/namespace/foo/");
@@ -136,7 +138,7 @@ public class Restful2ActionMapperTest extends StrutsInternalTestCase {
assertEquals(1, mapping.getParams().size());
assertEquals("1", mapping.getParams().get("bar"));
}
public void testPutUpdate() throws Exception {
req.setupGetRequestURI("/my/namespace/bar/1/foo/2");
@@ -153,7 +155,7 @@ public class Restful2ActionMapperTest extends StrutsInternalTestCase {
assertEquals(1, mapping.getParams().size());
assertEquals("1", mapping.getParams().get("bar"));
}
public void testPutUpdateWithIdParam() throws Exception {
mapper.setIdParameterName("id");
@@ -171,7 +173,7 @@ public class Restful2ActionMapperTest extends StrutsInternalTestCase {
assertEquals(2, mapping.getParams().size());
assertEquals("1", mapping.getParams().get("bar"));
assertEquals("2", mapping.getParams().get("id"));
}
public void testPutUpdateWithFakePut() throws Exception {
@@ -24,13 +24,13 @@ import java.util.Map;
import junit.framework.TestCase;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.views.jsp.StrutsMockHttpServletRequest;
/**
* Unit test for {@link RestfulActionMapper}.
*
*/
public class RestfulActionMapperTest extends TestCase {
public class RestfulActionMapperTest extends StrutsInternalTestCase {
private RestfulActionMapper mapper;
@@ -38,13 +38,13 @@ public class RestfulActionMapperTest extends TestCase {
ActionMapping am = new ActionMapping();
am.setName("view");
am.setNamespace("secure");
am.setParams(Collections.<String, Object>emptyMap());
am.setParams(Collections.emptyMap());
assertEquals("secureview", mapper.getUriFromActionMapping(am));
}
public void testGetUriParam() {
Map<String, Object> param = new HashMap<String, Object>();
Map<String, Object> param = new HashMap<>();
param.put("article", "123");
ActionMapping am = new ActionMapping();
am.setName("view");
@@ -55,7 +55,7 @@ public class RestfulActionMapperTest extends TestCase {
}
public void testGetUriParamId() {
Map<String, Object> param = new HashMap<String, Object>();
Map<String, Object> param = new HashMap<>();
param.put("article", "123");
param.put("viewId", "456");
ActionMapping am = new ActionMapping();
@@ -66,14 +66,14 @@ public class RestfulActionMapperTest extends TestCase {
assertEquals("secureview/456", mapper.getUriFromActionMapping(am));
}
public void testGetMappingNoSlash() throws Exception {
public void testGetMappingNoSlash() {
StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest();
request.setupGetServletPath("noslash");
assertNull(mapper.getMapping(request, null));
}
public void testGetMapping() throws Exception {
public void testGetMapping() {
StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest();
request.setupGetServletPath("/myapp/view/12");
@@ -83,7 +83,7 @@ public class RestfulActionMapperTest extends TestCase {
assertEquals("12", am.getParams().get("view"));
}
public void testGetMapping2() throws Exception {
public void testGetMapping2() {
StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest();
request.setupGetServletPath("/myapp/12/region/europe");
@@ -94,7 +94,7 @@ public class RestfulActionMapperTest extends TestCase {
assertEquals("europe", am.getParams().get("region"));
}
public void testGetMapping3() throws Exception {
public void testGetMapping3() {
StrutsMockHttpServletRequest request = new StrutsMockHttpServletRequest();
request.setupGetServletPath("/myapp/view/12/region/europe");
@@ -106,10 +106,13 @@ public class RestfulActionMapperTest extends TestCase {
}
protected void setUp() throws Exception {
super.setUp();
mapper = new RestfulActionMapper();
container.inject(mapper);
}
protected void tearDown() throws Exception {
super.tearDown();
mapper = null;
}
@@ -44,7 +44,6 @@ import static org.easymock.EasyMock.expect;
public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
public void testIncludeParameterInResultWithConditionParseOn() throws Exception {
ResultConfig resultConfig = new ResultConfig.Builder("", "")
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
@@ -53,15 +52,13 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
.addParam("location", "someLocation")
.addParam("prependServletContext", "true")
.addParam("method", "someMethod")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("param2", "${#value2}")
.addParam("param3", "${#value3}")
.addParam("anchor", "${#fragment}")
.build();
ActionContext context = ActionContext.getContext();
ValueStack stack = context.getValueStack();
context.getContextMap().put("value1", "value 1");
@@ -72,12 +69,11 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
context.put(ServletActionContext.HTTP_REQUEST, req);
context.put(ServletActionContext.HTTP_RESPONSE, res);
Map<String, ResultConfig> results= new HashMap<>();
Map<String, ResultConfig> results = new HashMap<>();
results.put("myResult", resultConfig);
ActionConfig actionConfig = new ActionConfig.Builder("", "", "")
.addResultConfigs(results).build();
.addResultConfigs(results).build();
ServletActionRedirectResult result = new ServletActionRedirectResult();
result.setActionName("myAction${1-1}");
@@ -98,7 +94,7 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
expect(mockInvocation.getStack()).andReturn(stack).anyTimes();
control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
container.inject(result);
result.execute(mockInvocation);
assertEquals("/myNamespace0/myAction0.action?param1=value+1&param2=value+2&param3=value+3#fragment", res.getRedirectedUrl());
@@ -106,23 +102,20 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
}
public void testExpressionParameterInResultWithConditionParseOn() throws Exception {
ResultConfig resultConfig = new ResultConfig.Builder("", "")
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
.addParam("encode", "true")
.addParam("parse", "true")
.addParam("location", "someLocation")
.addParam("prependServletContext", "true")
.addParam("method", "someMethod")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("param2", "${#value2}")
.addParam("param3", "${#value3}")
.addParam("anchor", "${#fragment}")
.build();
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
.addParam("encode", "true")
.addParam("parse", "true")
.addParam("location", "someLocation")
.addParam("prependServletContext", "true")
.addParam("method", "someMethod")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("param2", "${#value2}")
.addParam("param3", "${#value3}")
.addParam("anchor", "${#fragment}")
.build();
ActionContext context = ActionContext.getContext();
ValueStack stack = context.getValueStack();
@@ -137,12 +130,11 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
context.put(ServletActionContext.HTTP_REQUEST, req);
context.put(ServletActionContext.HTTP_RESPONSE, res);
Map<String, ResultConfig> results= new HashMap<>();
Map<String, ResultConfig> results = new HashMap<>();
results.put("myResult", resultConfig);
ActionConfig actionConfig = new ActionConfig.Builder("", "", "")
.addResultConfigs(results).build();
.addResultConfigs(results).build();
ServletActionRedirectResult result = new ServletActionRedirectResult();
result.setNamespace("/myNamespace${#namespaceName}");
@@ -166,7 +158,7 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
control.replay();
DefaultActionMapper mapper = (DefaultActionMapper) container.getInstance(ActionMapper.class);
mapper.setAllowDynamicMethodCalls("true");
result.setActionMapper(mapper);
container.inject(result);
result.execute(mockInvocation);
assertEquals("/myNamespace${1-1}/myAction${1-1}!myMethod${1-1}.action?param1=value+1&param2=value+2&param3=value+3#fragment", res.getRedirectedUrl());
@@ -181,23 +173,20 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
}
public void testIncludeParameterInResultWithConditionParseOnWithNoNamespace() throws Exception {
ResultConfig resultConfig = new ResultConfig.Builder("", "")
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
.addParam("encode", "true")
.addParam("parse", "true")
.addParam("location", "someLocation")
.addParam("prependServletContext", "true")
.addParam("method", "someMethod")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("param2", "${#value2}")
.addParam("param3", "${#value3}")
.addParam("anchor", "${#fragment}")
.build();
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
.addParam("encode", "true")
.addParam("parse", "true")
.addParam("location", "someLocation")
.addParam("prependServletContext", "true")
.addParam("method", "someMethod")
.addParam("statusCode", "333")
.addParam("param1", "${#value1}")
.addParam("param2", "${#value2}")
.addParam("param3", "${#value3}")
.addParam("anchor", "${#fragment}")
.build();
ActionContext context = ActionContext.getContext();
ValueStack stack = context.getValueStack();
@@ -209,12 +198,11 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
context.put(ServletActionContext.HTTP_REQUEST, req);
context.put(ServletActionContext.HTTP_RESPONSE, res);
Map<String, ResultConfig> results= new HashMap<>();
Map<String, ResultConfig> results = new HashMap<>();
results.put("myResult", resultConfig);
ActionConfig actionConfig = new ActionConfig.Builder("", "", "")
.addResultConfigs(results).build();
.addResultConfigs(results).build();
ServletActionRedirectResult result = new ServletActionRedirectResult();
result.setActionName("myAction${1-1}");
@@ -235,7 +223,7 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
expect(mockInvocation.getStack()).andReturn(stack).anyTimes();
control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
container.inject(result);
result.execute(mockInvocation);
assertEquals("/${1-1}/myAction0.action?param1=value+1&param2=value+2&param3=value+3#fragment", res.getRedirectedUrl());
@@ -243,7 +231,6 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
}
public void testIncludeParameterInResult() throws Exception {
ResultConfig resultConfig = new ResultConfig.Builder("", "")
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
@@ -264,12 +251,11 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
context.put(ServletActionContext.HTTP_REQUEST, req);
context.put(ServletActionContext.HTTP_RESPONSE, res);
Map<String, ResultConfig> results= new HashMap<>();
Map<String, ResultConfig> results = new HashMap<>();
results.put("myResult", resultConfig);
ActionConfig actionConfig = new ActionConfig.Builder("", "", "")
.addResultConfigs(results).build();
.addResultConfigs(results).build();
ServletActionRedirectResult result = new ServletActionRedirectResult();
result.setActionName("myAction");
@@ -289,7 +275,7 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
expect(mockInvocation.getInvocationContext()).andReturn(context);
control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
container.inject(result);
result.execute(mockInvocation);
assertEquals("/myNamespace/myAction.action?param1=value+1&param2=value+2&param3=value+3#fragment", res.getRedirectedUrl());
@@ -297,7 +283,6 @@ public class ServletActionRedirectResultTest extends StrutsInternalTestCase {
}
public void testBuildResultWithParameter() throws Exception {
ResultConfig resultConfig = new ResultConfig.Builder("", ServletActionRedirectResult.class.getName())
.addParam("actionName", "someActionName")
.addParam("namespace", "someNamespace")
@@ -251,7 +251,7 @@ public class ServletRedirectResultTest extends StrutsInternalTestCase implements
expect(mockInvocation.getInvocationContext()).andReturn(context);
control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
container.inject(result);
result.execute(mockInvocation);
assertEquals("/myNamespace/myAction.action?param1=value+1&param2=value+2&param3=value+3#fragment", res.getRedirectedUrl());
control.verify();
@@ -311,7 +311,7 @@ public class ServletRedirectResultTest extends StrutsInternalTestCase implements
expect(mockValueStack.getActionContext()).andReturn(actionContext);
control.replay();
result.setActionMapper(container.getInstance(ActionMapper.class));
container.inject(result);
result.execute(mockInvocation);
assertEquals("/myNamespace/myAction.action?param=value+1&param=value+2", res.getRedirectedUrl());
control.verify();
@@ -16,20 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.util;
package org.apache.struts2.url;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class URLDecoderUtilTest {
public class StrutsUrlDecoderTest {
private StrutsUrlDecoder decoder;
@Test
public void testURLDecodeStringInvalid() {
public void testDecodeStringInvalid() {
// %n rather than %nn should throw an IAE according to the Javadoc
Exception exception = null;
try {
URLDecoderUtil.decode("%5xxxxx", "ISO-8859-1");
decoder.decode("%5xxxxx", "ISO-8859-1", false);
} catch (Exception e) {
exception = e;
}
@@ -38,7 +43,7 @@ public class URLDecoderUtilTest {
// Edge case trying to trigger ArrayIndexOutOfBoundsException
exception = null;
try {
URLDecoderUtil.decode("%5", "ISO-8859-1");
decoder.decode("%5", "ISO-8859-1", false);
} catch (Exception e) {
exception = e;
}
@@ -46,51 +51,62 @@ public class URLDecoderUtilTest {
}
@Test
public void testURLDecodeStringValidIso88591Start() {
String result = URLDecoderUtil.decode("%41xxxx", "ISO-8859-1");
public void testDecodeStringValidIso88591Start() {
String result = decoder.decode("%41xxxx", "ISO-8859-1", false);
assertEquals("Axxxx", result);
}
@Test
public void testURLDecodeStringValidIso88591Middle() {
String result = URLDecoderUtil.decode("xx%41xx", "ISO-8859-1");
public void testDecodeStringValidIso88591Middle() {
String result = decoder.decode("xx%41xx", "ISO-8859-1", false);
assertEquals("xxAxx", result);
}
@Test
public void testURLDecodeStringValidIso88591End() {
String result = URLDecoderUtil.decode("xxxx%41", "ISO-8859-1");
public void testDecodeStringValidIso88591End() {
String result = decoder.decode("xxxx%41", "ISO-8859-1", false);
assertEquals("xxxxA", result);
}
@Test
public void testURLDecodeStringValidUtf8Start() {
String result = URLDecoderUtil.decode("%c3%aaxxxx", "UTF-8");
public void testDecodeStringValidUtf8Start() {
String result = decoder.decode("%c3%aaxxxx", "UTF-8", false);
assertEquals("\u00eaxxxx", result);
}
@Test
public void testURLDecodeStringValidUtf8Middle() {
String result = URLDecoderUtil.decode("xx%c3%aaxx", "UTF-8");
public void testDecodeStringValidUtf8Middle() {
String result = decoder.decode("xx%c3%aaxx", "UTF-8", false);
assertEquals("xx\u00eaxx", result);
}
@Test
public void testURLDecodeStringValidUtf8End() {
String result = URLDecoderUtil.decode("xxxx%c3%aa", "UTF-8");
public void testDecodeStringValidUtf8End() {
String result = decoder.decode("xxxx%c3%aa", "UTF-8", false);
assertEquals("xxxx\u00ea", result);
}
@Test
public void testURLDecodePlusCharAsSpace() {
String result = URLDecoderUtil.decode("a+b", "UTF-8", true);
public void testDecodePlusCharAsSpace() {
String result = decoder.decode("a+b", "UTF-8", true);
assertEquals("a b", result);
}
}
@Test
public void testDecodeNull() {
String result = decoder.decode(null);
assertNull(result);
}
@Test
public void testSettingEncoding() {
decoder.setEncoding("ISO-8859-1");
String result = decoder.decode("xxxx%41");
assertEquals("xxxxA", result);
}
@Before
public void setUp() throws Exception {
this.decoder = new StrutsUrlDecoder();
}
}
@@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.url;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class StrutsUrlEncoderTest {
private StrutsUrlEncoder encoder;
@Test
public void testEncodeValidIso88591Start() {
String result = encoder.encode("%xxxx", "ISO-8859-1");
assertEquals("%25xxxx", result);
}
@Test
public void testEncodeValidIso88591Middle() {
String result = encoder.encode("xx%xx", "ISO-8859-1");
assertEquals("xx%25xx", result);
}
@Test
public void testEncodeValidIso88591End() {
String result = encoder.encode("xxxx%", "ISO-8859-1");
assertEquals("xxxx%25", result);
}
@Test
public void testEncodeValidUtf8Start() {
String result = encoder.encode("\u00eaxxxx", "UTF-8");
assertEquals("%C3%AAxxxx", result);
}
@Test
public void testEncodeValidUtf8Middle() {
String result = encoder.encode("xx\u00eaxx", "UTF-8");
assertEquals("xx%C3%AAxx", result);
}
@Test
public void testEncodeValidUtf8End() {
String result = encoder.encode("xxxx\u00ea", "UTF-8");
assertEquals("xxxx%C3%AA", result);
}
@Test
public void testEncodePlusCharAsSpace() {
String result = encoder.encode("a b", "UTF-8");
assertEquals("a+b", result);
}
@Test
public void testEncodeException() {
String result = encoder.encode("a b", "UNKNOWN-8");
assertEquals("a b", result);
}
@Test
public void testSettingEncoding() {
encoder.setEncoding("ISO-8859-1");
String result = encoder.encode("%xxxx");
assertEquals("%25xxxx", result);
}
@Before
public void setUp() throws Exception {
this.encoder = new StrutsUrlEncoder();
}
}
@@ -1442,7 +1442,7 @@ public class SelectTest extends AbstractUITagTest {
return id;
}
}
private void prepareTagGeneric(SelectTag tag) {
TestAction testAction = (TestAction) action;
ArrayList collection = new ArrayList();
@@ -34,6 +34,8 @@ import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Scope.Strategy;
import org.apache.struts2.url.StrutsUrlDecoder;
import org.apache.struts2.url.StrutsUrlEncoder;
/**
@@ -41,7 +43,7 @@ import com.opensymphony.xwork2.inject.Scope.Strategy;
*
*/
public class DefaultUrlHelperTest extends StrutsInternalTestCase {
private StubContainer stubContainer;
private DefaultUrlHelper urlHelper;
@@ -204,7 +206,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
String urlString = urlHelper.buildUrl(actionName, (HttpServletRequest) mockHttpServletRequest.proxy(), (HttpServletResponse) mockHttpServletResponse.proxy(), params);
assertEquals(expectedString, urlString);
}
/**
* just one &, not &amp;
*/
@@ -429,21 +431,23 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
stubContainer = new StubContainer(container);
ActionContext.getContext().withContainer(stubContainer);
urlHelper = new DefaultUrlHelper();
urlHelper.setEncoder(new StrutsUrlEncoder());
urlHelper.setDecoder(new StrutsUrlDecoder());
}
private void setProp(String key, String val) {
stubContainer.overrides.put(key, val);
}
class StubContainer implements Container {
Container parent;
public StubContainer(Container parent) {
super();
this.parent = parent;
}
public Map<String, Object> overrides = new HashMap<String,Object>();
public <T> T getInstance(Class<T> type, String name) {
if (String.class.isAssignableFrom(type) && overrides.containsKey(name)) {
@@ -471,7 +475,7 @@ public class DefaultUrlHelperTest extends StrutsInternalTestCase {
public void removeScopeStrategy() {
parent.removeScopeStrategy();
}
public void setScopeStrategy(Strategy scopeStrategy) {
@@ -35,6 +35,8 @@ import junit.framework.TestCase;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.jasper.runtime.InstanceHelper;
import org.apache.struts2.url.StrutsUrlDecoder;
import org.apache.struts2.url.StrutsUrlEncoder;
import org.apache.struts2.views.util.DefaultUrlHelper;
import org.apache.struts2.views.util.UrlHelper;
import org.apache.tomcat.InstanceManager;
@@ -348,7 +350,9 @@ public class EmbeddedJSPResultTest extends TestCase {
EasyMock.expect(container.getInstanceNames(FileManager.class)).andReturn(new HashSet<>()).anyTimes();
EasyMock.expect(container.getInstance(FileManager.class)).andReturn(fileManager).anyTimes();
UrlHelper urlHelper = new DefaultUrlHelper();
DefaultUrlHelper urlHelper = new DefaultUrlHelper();
urlHelper.setDecoder(new StrutsUrlDecoder());
urlHelper.setEncoder(new StrutsUrlEncoder());
EasyMock.expect(container.getInstance(UrlHelper.class)).andReturn(urlHelper).anyTimes();
FileManagerFactory fileManagerFactory = new DummyFileManagerFactory();
EasyMock.expect(container.getInstance(FileManagerFactory.class)).andReturn(fileManagerFactory).anyTimes();
@@ -30,7 +30,6 @@
<%@ page import="org.apache.struts2.util.ComponentUtils" %>
<%@ page import="org.apache.struts2.util.ContainUtil" %>
<%@ page import="org.apache.struts2.util.StrutsUtil" %>
<%@ page import="org.apache.struts2.util.URLDecoderUtil" %>
<%@ page import="org.apache.struts2.views.velocity.VelocityStrutsUtil" %>
<%@ taglib prefix="r" uri="http://jakarta.apache.org/taglibs/request-1.0" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
@@ -61,7 +60,7 @@
<c:choose>
<c:when test="${false}">
Foo
</c:when>
</c:when>
<c:otherwise>
Bar
</c:otherwise>
@@ -79,7 +78,7 @@
BarBar
</c:otherwise>
</c:choose>
</c:when>
</c:when>
<c:otherwise>
Bar
</c:otherwise>
@@ -97,7 +96,7 @@
BarBar
</c:otherwise>
</c:choose>
</c:when>
</c:when>
<c:otherwise>
Bar
</c:otherwise>
@@ -107,7 +106,7 @@
<c:choose>
<c:when test="${false}">
Foo
</c:when>
</c:when>
<c:otherwise>
Bar
<c:choose>
@@ -125,7 +124,7 @@
<c:choose>
<c:when test="${false}">
Foo
</c:when>
</c:when>
<c:otherwise>
Bar
<c:choose>
@@ -188,7 +187,7 @@
</c:if>
<c:if test="${testvalue3=='value3'}">
testvalue3 set/if worked.<br/>
</c:if>
</c:if>
<c:if test="${testvalue4=='value4'}">
testvalue4 set/if worked.<br/>
</c:if>