PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/struts-2.2.1/src/xwork-core/src/main/java/com/opensymphony/xwork2/interceptor/ParametersInterceptor.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 437 lines | 236 code | 44 blank | 157 comment | 52 complexity | fe3a8fc39789f4c2634e7ca108d3e4f4 MD5 | raw file
  1. /*
  2. * Copyright 2002-2007,2009 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.opensymphony.xwork2.interceptor;
  17. import com.opensymphony.xwork2.ActionContext;
  18. import com.opensymphony.xwork2.ActionInvocation;
  19. import com.opensymphony.xwork2.ValidationAware;
  20. import com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler;
  21. import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
  22. import com.opensymphony.xwork2.inject.Inject;
  23. import com.opensymphony.xwork2.util.ClearableValueStack;
  24. import com.opensymphony.xwork2.util.LocalizedTextUtil;
  25. import com.opensymphony.xwork2.util.MemberAccessValueStack;
  26. import com.opensymphony.xwork2.util.TextParseUtil;
  27. import com.opensymphony.xwork2.util.ValueStack;
  28. import com.opensymphony.xwork2.util.ValueStackFactory;
  29. import com.opensymphony.xwork2.util.logging.Logger;
  30. import com.opensymphony.xwork2.util.logging.LoggerFactory;
  31. import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
  32. import java.util.Collection;
  33. import java.util.Collections;
  34. import java.util.Comparator;
  35. import java.util.HashSet;
  36. import java.util.Map;
  37. import java.util.Set;
  38. import java.util.TreeMap;
  39. import java.util.regex.Matcher;
  40. import java.util.regex.Pattern;
  41. /**
  42. * <!-- START SNIPPET: description -->
  43. * This interceptor sets all parameters on the value stack.
  44. * <p/>
  45. * This interceptor gets all parameters from {@link ActionContext#getParameters()} and sets them on the value stack by
  46. * calling {@link ValueStack#setValue(String, Object)}, typically resulting in the values submitted in a form
  47. * request being applied to an action in the value stack. Note that the parameter map must contain a String key and
  48. * often containers a String[] for the value.
  49. * <p/>
  50. * <p/> The interceptor takes one parameter named 'ordered'. When set to true action properties are guaranteed to be
  51. * set top-down which means that top action's properties are set first. Then it's subcomponents properties are set.
  52. * The reason for this order is to enable a 'factory' pattern. For example, let's assume that one has an action
  53. * that contains a property named 'modelClass' that allows to choose what is the underlying implementation of model.
  54. * By assuring that modelClass property is set before any model properties are set, it's possible to choose model
  55. * implementation during action.setModelClass() call. Similiarily it's possible to use action.setPrimaryKey()
  56. * property set call to actually load the model class from persistent storage. Without any assumption on parameter
  57. * order you have to use patterns like 'Preparable'.
  58. * <p/>
  59. * <p/> Because parameter names are effectively OGNL statements, it is important that security be taken in to account.
  60. * This interceptor will not apply any values in the parameters map if the expression contains an assignment (=),
  61. * multiple expressions (,), or references any objects in the context (#). This is all done in the {@link
  62. * #acceptableName(String)} method. In addition to this method, if the action being invoked implements the {@link
  63. * ParameterNameAware} interface, the action will be consulted to determine if the parameter should be set.
  64. * <p/>
  65. * <p/> In addition to these restrictions, a flag ({@link ReflectionContextState#DENY_METHOD_EXECUTION}) is set such that
  66. * no methods are allowed to be invoked. That means that any expression such as <i>person.doSomething()</i> or
  67. * <i>person.getName()</i> will be explicitely forbidden. This is needed to make sure that your application is not
  68. * exposed to attacks by malicious users.
  69. * <p/>
  70. * <p/> While this interceptor is being invoked, a flag ({@link ReflectionContextState#CREATE_NULL_OBJECTS}) is turned
  71. * on to ensure that any null reference is automatically created - if possible. See the type conversion documentation
  72. * and the {@link InstantiatingNullHandler} javadocs for more information.
  73. * <p/>
  74. * <p/> Finally, a third flag ({@link XWorkConverter#REPORT_CONVERSION_ERRORS}) is set that indicates any errors when
  75. * converting the the values to their final data type (String[] -&gt; int) an unrecoverable error occured. With this
  76. * flag set, the type conversion errors will be reported in the action context. See the type conversion documentation
  77. * and the {@link XWorkConverter} javadocs for more information.
  78. * <p/>
  79. * <p/> If you are looking for detailed logging information about your parameters, turn on DEBUG level logging for this
  80. * interceptor. A detailed log of all the parameter keys and values will be reported.
  81. * <p/>
  82. * <p/>
  83. * <b>Note:</b> Since XWork 2.0.2, this interceptor extends {@link MethodFilterInterceptor}, therefore being
  84. * able to deal with excludeMethods / includeMethods parameters. See [Workflow Interceptor]
  85. * (class {@link DefaultWorkflowInterceptor}) for documentation and examples on how to use this feature.
  86. * <p/>
  87. * <!-- END SNIPPET: description -->
  88. * <p/>
  89. * <p/> <u>Interceptor parameters:</u>
  90. * <p/>
  91. * <!-- START SNIPPET: parameters -->
  92. * <p/>
  93. * <ul>
  94. * <p/>
  95. * <li>ordered - set to true if you want the top-down property setter behaviour</li>
  96. * <p/>
  97. * </ul>
  98. * <p/>
  99. * <!-- END SNIPPET: parameters -->
  100. * <p/>
  101. * <p/> <u>Extending the interceptor:</u>
  102. * <p/>
  103. * <!-- START SNIPPET: extending -->
  104. * <p/>
  105. * <p/> The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
  106. * actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
  107. * this interceptor and override the {@link #acceptableName(String)} method.
  108. * <p/>
  109. * <!-- END SNIPPET: extending -->
  110. * <p/>
  111. * <p/> <u>Example code:</u>
  112. * <p/>
  113. * <pre>
  114. * <!-- START SNIPPET: example -->
  115. * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
  116. * &lt;interceptor-ref name="params"/&gt;
  117. * &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
  118. * &lt;/action&gt;
  119. * <!-- END SNIPPET: example -->
  120. * </pre>
  121. *
  122. * @author Patrick Lightbody
  123. */
  124. public class ParametersInterceptor extends MethodFilterInterceptor {
  125. private static final Logger LOG = LoggerFactory.getLogger(ParametersInterceptor.class);
  126. boolean ordered = false;
  127. Set<Pattern> excludeParams = Collections.emptySet();
  128. Set<Pattern> acceptParams = Collections.emptySet();
  129. static boolean devMode = false;
  130. // Allowed names of parameters
  131. private String acceptedParamNames = "[a-zA-Z0-9\\.\\]\\[\\(\\)_'\\s]+";
  132. private Pattern acceptedPattern = Pattern.compile(acceptedParamNames);
  133. private ValueStackFactory valueStackFactory;
  134. @Inject
  135. public void setValueStackFactory(ValueStackFactory valueStackFactory) {
  136. this.valueStackFactory = valueStackFactory;
  137. }
  138. @Inject("devMode")
  139. public static void setDevMode(String mode) {
  140. devMode = "true".equals(mode);
  141. }
  142. public void setAcceptParamNames(String commaDelim) {
  143. Collection<String> acceptPatterns = asCollection(commaDelim);
  144. if (acceptPatterns != null) {
  145. acceptParams = new HashSet<Pattern>();
  146. for (String pattern : acceptPatterns) {
  147. acceptParams.add(Pattern.compile(pattern));
  148. }
  149. }
  150. }
  151. /**
  152. * Compares based on number of '.' characters (fewer is higher)
  153. */
  154. static final Comparator<String> rbCollator = new Comparator<String>() {
  155. public int compare(String s1, String s2) {
  156. int l1 = 0, l2 = 0;
  157. for (int i = s1.length() - 1; i >= 0; i--) {
  158. if (s1.charAt(i) == '.') l1++;
  159. }
  160. for (int i = s2.length() - 1; i >= 0; i--) {
  161. if (s2.charAt(i) == '.') l2++;
  162. }
  163. return l1 < l2 ? -1 : (l2 < l1 ? 1 : s1.compareTo(s2));
  164. }
  165. };
  166. @Override
  167. public String doIntercept(ActionInvocation invocation) throws Exception {
  168. Object action = invocation.getAction();
  169. if (!(action instanceof NoParameters)) {
  170. ActionContext ac = invocation.getInvocationContext();
  171. final Map<String, Object> parameters = retrieveParameters(ac);
  172. if (LOG.isDebugEnabled()) {
  173. LOG.debug("Setting params " + getParameterLogMap(parameters));
  174. }
  175. if (parameters != null) {
  176. Map<String, Object> contextMap = ac.getContextMap();
  177. try {
  178. ReflectionContextState.setCreatingNullObjects(contextMap, true);
  179. ReflectionContextState.setDenyMethodExecution(contextMap, true);
  180. ReflectionContextState.setReportingConversionErrors(contextMap, true);
  181. ValueStack stack = ac.getValueStack();
  182. setParameters(action, stack, parameters);
  183. } finally {
  184. ReflectionContextState.setCreatingNullObjects(contextMap, false);
  185. ReflectionContextState.setDenyMethodExecution(contextMap, false);
  186. ReflectionContextState.setReportingConversionErrors(contextMap, false);
  187. }
  188. }
  189. }
  190. return invocation.invoke();
  191. }
  192. /**
  193. * Gets the parameter map to apply from wherever appropriate
  194. *
  195. * @param ac The action context
  196. * @return The parameter map to apply
  197. */
  198. protected Map<String, Object> retrieveParameters(ActionContext ac) {
  199. return ac.getParameters();
  200. }
  201. /**
  202. * Adds the parameters into context's ParameterMap
  203. *
  204. * @param ac The action context
  205. * @param newParams The parameter map to apply
  206. * <p/>
  207. * In this class this is a no-op, since the parameters were fetched from the same location.
  208. * In subclasses both retrieveParameters() and addParametersToContext() should be overridden.
  209. */
  210. protected void addParametersToContext(ActionContext ac, Map<String, Object> newParams) {
  211. }
  212. protected void setParameters(Object action, ValueStack stack, final Map<String, Object> parameters) {
  213. ParameterNameAware parameterNameAware = (action instanceof ParameterNameAware)
  214. ? (ParameterNameAware) action : null;
  215. Map<String, Object> params;
  216. Map<String, Object> acceptableParameters;
  217. if (ordered) {
  218. params = new TreeMap<String, Object>(getOrderedComparator());
  219. acceptableParameters = new TreeMap<String, Object>(getOrderedComparator());
  220. params.putAll(parameters);
  221. } else {
  222. params = new TreeMap<String, Object>(parameters);
  223. acceptableParameters = new TreeMap<String, Object>();
  224. }
  225. for (Map.Entry<String, Object> entry : params.entrySet()) {
  226. String name = entry.getKey();
  227. boolean acceptableName = acceptableName(name)
  228. && (parameterNameAware == null
  229. || parameterNameAware.acceptableParameterName(name));
  230. if (acceptableName) {
  231. acceptableParameters.put(name, entry.getValue());
  232. }
  233. }
  234. ValueStack newStack = valueStackFactory.createValueStack(stack);
  235. boolean clearableStack = newStack instanceof ClearableValueStack;
  236. if (clearableStack) {
  237. //if the stack's context can be cleared, do that to prevent OGNL
  238. //from having access to objects in the stack, see XW-641
  239. ((ClearableValueStack)newStack).clearContextValues();
  240. Map<String, Object> context = newStack.getContext();
  241. ReflectionContextState.setCreatingNullObjects(context, true);
  242. ReflectionContextState.setDenyMethodExecution(context, true);
  243. ReflectionContextState.setReportingConversionErrors(context, true);
  244. //keep locale from original context
  245. context.put(ActionContext.LOCALE, stack.getContext().get(ActionContext.LOCALE));
  246. }
  247. boolean memberAccessStack = newStack instanceof MemberAccessValueStack;
  248. if (memberAccessStack) {
  249. //block or allow access to properties
  250. //see WW-2761 for more details
  251. MemberAccessValueStack accessValueStack = (MemberAccessValueStack) newStack;
  252. accessValueStack.setAcceptProperties(acceptParams);
  253. accessValueStack.setExcludeProperties(excludeParams);
  254. }
  255. for (Map.Entry<String, Object> entry : acceptableParameters.entrySet()) {
  256. String name = entry.getKey();
  257. Object value = entry.getValue();
  258. try {
  259. newStack.setValue(name, value);
  260. } catch (RuntimeException e) {
  261. if (devMode) {
  262. String developerNotification = LocalizedTextUtil.findText(ParametersInterceptor.class, "devmode.notification", ActionContext.getContext().getLocale(), "Developer Notification:\n{0}", new Object[]{
  263. "Unexpected Exception caught setting '" + name + "' on '" + action.getClass() + ": " + e.getMessage()
  264. });
  265. LOG.error(developerNotification);
  266. if (action instanceof ValidationAware) {
  267. ((ValidationAware) action).addActionMessage(developerNotification);
  268. }
  269. }
  270. }
  271. }
  272. if (clearableStack && (stack.getContext() != null) && (newStack.getContext() != null))
  273. stack.getContext().put(ActionContext.CONVERSION_ERRORS, newStack.getContext().get(ActionContext.CONVERSION_ERRORS));
  274. addParametersToContext(ActionContext.getContext(), acceptableParameters);
  275. }
  276. /**
  277. * Gets an instance of the comparator to use for the ordered sorting. Override this
  278. * method to customize the ordering of the parameters as they are set to the
  279. * action.
  280. *
  281. * @return A comparator to sort the parameters
  282. */
  283. protected Comparator<String> getOrderedComparator() {
  284. return rbCollator;
  285. }
  286. private String getParameterLogMap(Map<String, Object> parameters) {
  287. if (parameters == null) {
  288. return "NONE";
  289. }
  290. StringBuilder logEntry = new StringBuilder();
  291. for (Map.Entry entry : parameters.entrySet()) {
  292. logEntry.append(String.valueOf(entry.getKey()));
  293. logEntry.append(" => ");
  294. if (entry.getValue() instanceof Object[]) {
  295. Object[] valueArray = (Object[]) entry.getValue();
  296. logEntry.append("[ ");
  297. if (valueArray.length > 0 ) {
  298. for (int indexA = 0; indexA < (valueArray.length - 1); indexA++) {
  299. Object valueAtIndex = valueArray[indexA];
  300. logEntry.append(String.valueOf(valueAtIndex));
  301. logEntry.append(", ");
  302. }
  303. logEntry.append(String.valueOf(valueArray[valueArray.length - 1]));
  304. }
  305. logEntry.append(" ] ");
  306. } else {
  307. logEntry.append(String.valueOf(entry.getValue()));
  308. }
  309. }
  310. return logEntry.toString();
  311. }
  312. protected boolean acceptableName(String name) {
  313. if (isAccepted(name) && !isExcluded(name)) {
  314. return true;
  315. }
  316. return false;
  317. }
  318. protected boolean isAccepted(String paramName) {
  319. if (!this.acceptParams.isEmpty()) {
  320. for (Pattern pattern : acceptParams) {
  321. Matcher matcher = pattern.matcher(paramName);
  322. if (matcher.matches()) {
  323. return true;
  324. }
  325. }
  326. return false;
  327. } else
  328. return acceptedPattern.matcher(paramName).matches();
  329. }
  330. protected boolean isExcluded(String paramName) {
  331. if (!this.excludeParams.isEmpty()) {
  332. for (Pattern pattern : excludeParams) {
  333. Matcher matcher = pattern.matcher(paramName);
  334. if (matcher.matches()) {
  335. return true;
  336. }
  337. }
  338. }
  339. return false;
  340. }
  341. /**
  342. * Whether to order the parameters or not
  343. *
  344. * @return True to order
  345. */
  346. public boolean isOrdered() {
  347. return ordered;
  348. }
  349. /**
  350. * Set whether to order the parameters by object depth or not
  351. *
  352. * @param ordered True to order them
  353. */
  354. public void setOrdered(boolean ordered) {
  355. this.ordered = ordered;
  356. }
  357. /**
  358. * Gets a set of regular expressions of parameters to remove
  359. * from the parameter map
  360. *
  361. * @return A set of compiled regular expression patterns
  362. */
  363. protected Set getExcludeParamsSet() {
  364. return excludeParams;
  365. }
  366. /**
  367. * Sets a comma-delimited list of regular expressions to match
  368. * parameters that should be removed from the parameter map.
  369. *
  370. * @param commaDelim A comma-delimited list of regular expressions
  371. */
  372. public void setExcludeParams(String commaDelim) {
  373. Collection<String> excludePatterns = asCollection(commaDelim);
  374. if (excludePatterns != null) {
  375. excludeParams = new HashSet<Pattern>();
  376. for (String pattern : excludePatterns) {
  377. excludeParams.add(Pattern.compile(pattern));
  378. }
  379. }
  380. }
  381. /**
  382. * Return a collection from the comma delimited String.
  383. *
  384. * @param commaDelim the comma delimited String.
  385. * @return A collection from the comma delimited String. Returns <tt>null</tt> if the string is empty.
  386. */
  387. private Collection<String> asCollection(String commaDelim) {
  388. if (commaDelim == null || commaDelim.trim().length() == 0) {
  389. return null;
  390. }
  391. return TextParseUtil.commaDelimitedStringToSet(commaDelim);
  392. }
  393. }