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