diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java index 49308d263..2ac045d45 100644 --- a/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java +++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java @@ -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()); diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java index f37a85078..5dff4724c 100644 --- a/core/src/main/java/org/apache/struts2/StrutsConstants.java +++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java @@ -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"; } diff --git a/core/src/main/java/org/apache/struts2/config/AbstractBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/AbstractBeanSelectionProvider.java index 34db87d3a..f672ae041 100644 --- a/core/src/main/java/org/apache/struts2/config/AbstractBeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/AbstractBeanSelectionProvider.java @@ -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; diff --git a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java index f47bbc354..2e5a9315c 100644 --- a/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java +++ b/core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java @@ -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); } diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java index 2dc418241..1a11b06bf 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapper.java @@ -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 + * Restful2ActionMapper */ 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()); + mapping.setParams(new HashMap<>()); } mapping.getParams().putAll(parameters); } diff --git a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java index 888bb97a4..1709834a1 100644 --- a/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java +++ b/core/src/main/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapper.java @@ -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 + * RestfulActionMapper */ 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(); } diff --git a/core/src/main/java/org/apache/struts2/url/StrutsUrlDecoder.java b/core/src/main/java/org/apache/struts2/url/StrutsUrlDecoder.java new file mode 100644 index 000000000..3a480d87f --- /dev/null +++ b/core/src/main/java/org/apache/struts2/url/StrutsUrlDecoder.java @@ -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"); + } + +} diff --git a/core/src/main/java/org/apache/struts2/url/StrutsUrlEncoder.java b/core/src/main/java/org/apache/struts2/url/StrutsUrlEncoder.java new file mode 100644 index 000000000..12b5a0104 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/url/StrutsUrlEncoder.java @@ -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); + } +} diff --git a/core/src/main/java/org/apache/struts2/url/UrlDecoder.java b/core/src/main/java/org/apache/struts2/url/UrlDecoder.java new file mode 100644 index 000000000..b54c563f5 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/url/UrlDecoder.java @@ -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); + +} diff --git a/core/src/main/java/org/apache/struts2/url/UrlEncoder.java b/core/src/main/java/org/apache/struts2/url/UrlEncoder.java new file mode 100644 index 000000000..976645a53 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/url/UrlEncoder.java @@ -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); + +} diff --git a/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java b/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java deleted file mode 100644 index 04f58e157..000000000 --- a/core/src/main/java/org/apache/struts2/util/URLDecoderUtil.java +++ /dev/null @@ -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 x-www-form-urlencoded string. - * @param sequence the String to decode - * @param charset The name of a supported character encoding. - * @return the newly decoded String - * @exception IllegalArgumentException If the encoding is not valid - */ - public static String decode(String sequence, String charset) { - return UDecoder.URLDecode(sequence, charset); - } - - /** - * Decodes a x-www-form-urlencoded 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 true other decoding rules apply. - * @return the newly decoded String - * @exception IllegalArgumentException If the encoding is not valid - */ - public static String decode(String sequence, String charset, boolean isQueryString) { - return UDecoder.URLDecode(sequence, charset, isQueryString); - } - -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java deleted file mode 100644 index 13be1ef6d..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/Ascii.java +++ /dev/null @@ -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; - } - -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java deleted file mode 100644 index 00581cb5c..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/B2CConverter.java +++ /dev/null @@ -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 encodingToCharsetCache = - new HashMap(); - - 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()); - } - } - } -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java deleted file mode 100644 index 49cb3cde4..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/ByteChunk.java +++ /dev/null @@ -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. - *
- * 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. - *
- * 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; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java deleted file mode 100644 index c612abe49..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/CharChunk.java +++ /dev/null @@ -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; - } - -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java deleted file mode 100644 index 461675276..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/HexUtils.java +++ /dev/null @@ -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; - } -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/MessageBytes.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/MessageBytes.java deleted file mode 100644 index db7ab8a71..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/MessageBytes.java +++ /dev/null @@ -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<->byte conversions. - */ - public Charset getCharset() { - return byteC.getCharset(); - } - - /** - * Set the Charset used for string<->byte conversions. - */ - public void setCharset(Charset charset) { - byteC.setCharset(charset); - } - - /** Do a char->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 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(); - } - } -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/StringCache.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/StringCache.java deleted file mode 100644 index d568ea5fa..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/StringCache.java +++ /dev/null @@ -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 bcStats = - new HashMap(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 ccStats = - new HashMap(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> tempMap = - new TreeMap>(); - for (Entry 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 list = tempMap.get(count); - if (list == null) { - // Create list - list = new ArrayList(); - 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 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> tempMap = - new TreeMap>(); - for (Entry 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 list = tempMap.get(count); - if (list == null) { - // Create list - list = new ArrayList(); - 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 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; - } - - } - - -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/UDecoder.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/UDecoder.java deleted file mode 100644 index 1719986b2..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/UDecoder.java +++ /dev/null @@ -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 ) { - 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 ) { - // 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; - } -} diff --git a/core/src/main/java/org/apache/struts2/util/tomcat/buf/Utf8Decoder.java b/core/src/main/java/org/apache/struts2/util/tomcat/buf/Utf8Decoder.java deleted file mode 100644 index bca4de8d6..000000000 --- a/core/src/main/java/org/apache/struts2/util/tomcat/buf/Utf8Decoder.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java b/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java index c27361177..9f383c611 100644 --- a/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java +++ b/core/src/main/java/org/apache/struts2/views/util/DefaultUrlHelper.java @@ -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 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 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 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, " 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 true 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 parseQueryString(String queryString, boolean forceValueArray) { - Map queryParams = new LinkedHashMap(); + Map 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 paramList = new ArrayList(Arrays.asList(currentParamValues)); + List 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}); } diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties index 753a80b8d..07f362a7a 100644 --- a/core/src/main/resources/org/apache/struts2/default.properties +++ b/core/src/main/resources/org/apache/struts2/default.properties @@ -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 diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 467b9007c..7980af2f2 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -313,6 +313,11 @@ + + + diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapperTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapperTest.java index 5a6e8af5c..d792b227e 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapperTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/Restful2ActionMapperTest.java @@ -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 { diff --git a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java index e327b6556..0ee3b1956 100644 --- a/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java +++ b/core/src/test/java/org/apache/struts2/dispatcher/mapper/RestfulActionMapperTest.java @@ -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.emptyMap()); + am.setParams(Collections.emptyMap()); assertEquals("secureview", mapper.getUriFromActionMapping(am)); } public void testGetUriParam() { - Map param = new HashMap(); + Map 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 param = new HashMap(); + Map 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; } diff --git a/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java b/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java index 216bb98e0..0933f8ea7 100644 --- a/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java +++ b/core/src/test/java/org/apache/struts2/result/ServletActionRedirectResultTest.java @@ -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 results= new HashMap<>(); + Map 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¶m2=value+2¶m3=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 results= new HashMap<>(); + Map 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¶m2=value+2¶m3=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 results= new HashMap<>(); + Map 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¶m2=value+2¶m3=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 results= new HashMap<>(); + Map 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¶m2=value+2¶m3=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") diff --git a/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java b/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java index 321b3b525..8936783fc 100644 --- a/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java +++ b/core/src/test/java/org/apache/struts2/result/ServletRedirectResultTest.java @@ -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¶m2=value+2¶m3=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¶m=value+2", res.getRedirectedUrl()); control.verify(); diff --git a/core/src/test/java/org/apache/struts2/util/URLDecoderUtilTest.java b/core/src/test/java/org/apache/struts2/url/StrutsUrlDecoderTest.java similarity index 51% rename from core/src/test/java/org/apache/struts2/util/URLDecoderUtilTest.java rename to core/src/test/java/org/apache/struts2/url/StrutsUrlDecoderTest.java index 3d75860ee..5dd44f897 100644 --- a/core/src/test/java/org/apache/struts2/util/URLDecoderUtilTest.java +++ b/core/src/test/java/org/apache/struts2/url/StrutsUrlDecoderTest.java @@ -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); } -} \ No newline at end of file + @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(); + } +} diff --git a/core/src/test/java/org/apache/struts2/url/StrutsUrlEncoderTest.java b/core/src/test/java/org/apache/struts2/url/StrutsUrlEncoderTest.java new file mode 100644 index 000000000..95aaacda6 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/url/StrutsUrlEncoderTest.java @@ -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(); + } + +} diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/SelectTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/SelectTest.java index e739ffe01..a087da478 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/SelectTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/SelectTest.java @@ -1442,7 +1442,7 @@ public class SelectTest extends AbstractUITagTest { return id; } } - + private void prepareTagGeneric(SelectTag tag) { TestAction testAction = (TestAction) action; ArrayList collection = new ArrayList(); diff --git a/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java b/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java index 9266e4923..3e70e7a44 100644 --- a/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java +++ b/core/src/test/java/org/apache/struts2/views/util/DefaultUrlHelperTest.java @@ -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 & */ @@ -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 overrides = new HashMap(); public T getInstance(Class 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) { diff --git a/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java index a5b3bb254..6cde3f7d6 100644 --- a/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java +++ b/plugins/embeddedjsp/src/test/java/org/apache/struts2/EmbeddedJSPResultTest.java @@ -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(); diff --git a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/complex0.jsp b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/complex0.jsp index 184ce1781..440898140 100644 --- a/plugins/embeddedjsp/src/test/resources/org/apache/struts2/complex0.jsp +++ b/plugins/embeddedjsp/src/test/resources/org/apache/struts2/complex0.jsp @@ -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 @@ Foo - + Bar @@ -79,7 +78,7 @@ BarBar - + Bar @@ -97,7 +96,7 @@ BarBar - + Bar @@ -107,7 +106,7 @@ Foo - + Bar @@ -125,7 +124,7 @@ Foo - + Bar @@ -188,7 +187,7 @@ testvalue3 set/if worked.
-
+ testvalue4 set/if worked.