Compare commits

..

22 Commits

Author SHA1 Message Date
Lukasz Lenart 4603706b40 [maven-release-plugin] prepare release STRUTS_7_0_3 2025-02-17 10:41:17 +01:00
Lukasz Lenart 556522e9b2 WW-5529 Drops unused misleading setter setMaxLength in favour of setMaxlength (#1221) 2025-02-17 10:09:14 +01:00
Lukasz Lenart 1db8a72bb5 WW-5525 Fixes NPE when checking if expressions is acceptable (#1201)
* WW-5525 Fixes NPE when checking if expressions is acceptable

* WW-5525 Fixes bugs introduced by previous commit
2025-02-17 08:53:42 +01:00
Kusal Kithul-Godage 31c3fc50ed Merge pull request #1214 from apache/WW-5525-proxyutil-npe
WW-5525 Fix NPE in ProxyUtil for SecurityMemberAccess originating static members
2025-02-17 17:10:48 +11:00
dependabot[bot] adcd1df0ca Bump github/codeql-action from 3.28.8 to 3.28.9 (#1217)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.8...v3.28.9)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-16 12:06:16 +01:00
Lukasz Lenart 568b292bd4 WW-5501 Reverts changes related to WW-5501 (#1219) 2025-02-16 11:42:09 +01:00
Kusal Kithul-Godage 02e17f5912 Merge pull request #1213 from apache/WW-5528-multipart-illegal-char-errors
WW-5528 Ensure multipart upload illegal characters reported as error
2025-02-06 18:36:41 +11:00
Kusal Kithul-Godage 3c856c92a1 WW-5525 Fix NPE in ProxyUtil for SecurityMemberAccess originating static members 2025-02-06 12:23:42 +11:00
Kusal Kithul-Godage ff249c64cd WW-5528 Ensure multipart upload illegal characters reported as error 2025-02-06 11:48:42 +11:00
Lukasz Lenart a1de1cfdeb [maven-release-plugin] prepare for next development iteration 2025-02-04 07:07:24 +01:00
Lukasz Lenart 9326279769 [maven-release-plugin] prepare release STRUTS_7_0_2 2025-02-04 07:07:15 +01:00
Kusal Kithul-Godage 9474ba41f5 Merge pull request #1209 from apache/WW-5516-attrmap-npe
WW-5526 Fix AttributeMap NPE when PageContext has no request
2025-02-04 01:12:26 +11:00
Kusal Kithul-Godage 2d8433d300 WW-5516 Fix AttributeMap NPE when PageContext has no request 2025-02-04 00:51:51 +11:00
Greg Huber abbb2aad2c Fix inject 2025-02-03 14:49:45 +01:00
Lukasz Lenart 21c7ecc105 WW-5501 Ignores DMI related action field 2025-02-03 14:49:45 +01:00
Lukasz Lenart 58b0d536e8 Merge pull request #1207 from apache/dependabot/maven/org.htmlunit-htmlunit-4.9.0
Bump org.htmlunit:htmlunit from 4.2.0 to 4.9.0
2025-02-03 07:17:58 +01:00
Lukasz Lenart 442b4ea2e6 Merge pull request #1205 from apache/dependabot/github_actions/github/codeql-action-3.28.8
Bump github/codeql-action from 3.28.5 to 3.28.8
2025-02-03 07:17:24 +01:00
Lukasz Lenart f4d6564bbb Merge pull request #1204 from apache/fix/improves-code-quality
Improves code quality
2025-02-03 07:16:47 +01:00
dependabot[bot] 0b4855ac41 Bump org.htmlunit:htmlunit from 4.2.0 to 4.9.0
Bumps [org.htmlunit:htmlunit](https://github.com/HtmlUnit/htmlunit) from 4.2.0 to 4.9.0.
- [Release notes](https://github.com/HtmlUnit/htmlunit/releases)
- [Commits](https://github.com/HtmlUnit/htmlunit/compare/4.2.0...4.9.0)

---
updated-dependencies:
- dependency-name: org.htmlunit:htmlunit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 01:33:36 +00:00
dependabot[bot] 9d5d802980 Bump github/codeql-action from 3.28.5 to 3.28.8
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.5 to 3.28.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.5...v3.28.8)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 01:10:03 +00:00
Lukasz Lenart b34665046c Improves code quality
Uses existing function to split input using comma
2025-02-02 17:22:42 +01:00
Lukasz Lenart 080263e93f [maven-release-plugin] prepare for next development iteration 2025-02-02 08:26:01 +01:00
45 changed files with 402 additions and 414 deletions
+3 -3
View File
@@ -52,12 +52,12 @@ jobs:
java-version: 17
cache: 'maven'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.5
uses: github/codeql-action/init@v3.28.9
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3.28.5
uses: github/codeql-action/autobuild@v3.28.9
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.5
uses: github/codeql-action/analyze@v3.28.9
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -65,6 +65,6 @@ jobs:
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e7c0c9d71b7bd108fd12e06b56fc58d3d154164d # 2.22.11
uses: github/codeql-action/upload-sarif@0a35e8f6866a39b001e5f7ad1d0daf9836786896 # 2.22.11
with:
sarif_file: results.sarif
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-apps</artifactId>
<packaging>pom</packaging>
+2 -2
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>7.0.1</version>
<version>7.0.3</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-showcase</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-assembly</artifactId>
+4 -4
View File
@@ -25,11 +25,11 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-bom</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -44,7 +44,7 @@
</licenses>
<properties>
<struts-version.version>7.0.1</struts-version.version>
<struts-version.version>7.0.3</struts-version.version>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
@@ -160,7 +160,7 @@
</dependencyManagement>
<scm>
<tag>STRUTS_7_0_1</tag>
<tag>STRUTS_7_0_3</tag>
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -18,13 +18,12 @@
*/
package org.apache.struts2.components;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* <!-- START SNIPPET: javadoc -->
* <p>Render an HTML input field of type text</p>
@@ -50,17 +49,16 @@ import jakarta.servlet.http.HttpServletResponse;
* </pre>
*/
@StrutsTag(
name="textfield",
tldTagClass="org.apache.struts2.views.jsp.ui.TextFieldTag",
description="Render an HTML input field of type text",
allowDynamicAttributes=true)
name = "textfield",
tldTagClass = "org.apache.struts2.views.jsp.ui.TextFieldTag",
description = "Render an HTML input field of type text",
allowDynamicAttributes = true)
public class TextField extends UIBean {
/**
* The name of the default template for the TextFieldTag
*/
final public static String TEMPLATE = "text";
protected String maxlength;
protected String readonly;
protected String size;
@@ -95,27 +93,22 @@ public class TextField extends UIBean {
}
@StrutsTagAttribute(description="HTML maxlength attribute", type="Integer")
@StrutsTagAttribute(description = "HTML maxlength attribute", type = "Integer")
public void setMaxlength(String maxlength) {
this.maxlength = maxlength;
}
@StrutsTagAttribute(description="Deprecated. Use maxlength instead.", type="Integer")
public void setMaxLength(String maxlength) {
this.maxlength = maxlength;
}
@StrutsTagAttribute(description="Whether the input is readonly", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether the input is readonly", type = "Boolean", defaultValue = "false")
public void setReadonly(String readonly) {
this.readonly = readonly;
}
@StrutsTagAttribute(description="HTML size attribute", type="Integer")
@StrutsTagAttribute(description = "HTML size attribute", type = "Integer")
public void setSize(String size) {
this.size = size;
}
@StrutsTagAttribute(description="Specifies the html5 type element to display. e.g. text, email, url", defaultValue="text")
@StrutsTagAttribute(description = "Specifies the html5 type element to display. e.g. text, email, url", defaultValue = "text")
public void setType(String type) {
this.type = type;
}
@@ -18,9 +18,9 @@
*/
package org.apache.struts2.dispatcher;
import jakarta.servlet.jsp.PageContext;
import org.apache.struts2.StrutsStatics;
import jakarta.servlet.jsp.PageContext;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
@@ -86,7 +86,7 @@ public class AttributeMap extends AbstractMap<String, Object> {
PageContext pc = getPageContext();
if (pc == null) {
if (pc == null || pc.getRequest() == null) {
RequestMap request = (RequestMap) context.get(DispatcherConstants.REQUEST);
SessionMap session = (SessionMap) context.get(DispatcherConstants.SESSION);
ApplicationMap application = (ApplicationMap) context.get(DispatcherConstants.APPLICATION);
@@ -19,8 +19,6 @@
package org.apache.struts2.dispatcher.multipart;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.security.DefaultExcludedPatternsChecker;
import org.apache.struts2.security.ExcludedPatternsChecker;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
import org.apache.commons.fileupload2.core.FileUploadContentTypeException;
@@ -28,6 +26,7 @@ import org.apache.commons.fileupload2.core.FileUploadException;
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
import org.apache.commons.fileupload2.core.FileUploadSizeException;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -54,8 +53,6 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
private static final Logger LOG = LogManager.getLogger(AbstractMultiPartRequest.class);
private static final String EXCLUDED_FILE_PATTERN = ".*[<>&\"'|;\\\\/?*:]+.*|.*\\.\\..*";
/**
* Defines the internal buffer size used during streaming operations.
*/
@@ -111,14 +108,6 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
*/
protected Map<String, List<String>> parameters = new HashMap<>();
private final ExcludedPatternsChecker patternsChecker;
protected AbstractMultiPartRequest() {
patternsChecker = new DefaultExcludedPatternsChecker();
((DefaultExcludedPatternsChecker) patternsChecker).setAdditionalExcludePatterns(EXCLUDED_FILE_PATTERN);
}
/**
* @param bufferSize Sets the buffer size to be used.
*/
@@ -296,16 +285,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
* @return the canonical name based on the supplied filename
*/
protected String getCanonicalName(final String originalFileName) {
String fileName = originalFileName;
int forwardSlash = fileName.lastIndexOf('/');
int backwardSlash = fileName.lastIndexOf('\\');
if (forwardSlash != -1 && forwardSlash > backwardSlash) {
fileName = fileName.substring(forwardSlash + 1);
} else {
fileName = fileName.substring(backwardSlash + 1);
}
return fileName;
return FilenameUtils.getName(originalFileName);
}
/**
@@ -429,12 +409,4 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
}
}
/**
* @param fileName file name to check
* @return true if the file name is excluded
*/
protected boolean isExcluded(String fileName) {
return patternsChecker.isExcluded(fileName).isExcluded();
}
}
@@ -77,12 +77,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
protected void processNormalFormField(DiskFileItem item, Charset charset) throws IOException {
LOG.debug("Item: {} is a normal form field", item.getName());
if (isExcluded(item.getFieldName())) {
LOG.warn(() -> "Form field [%s] is rejected!".formatted(normalizeSpace(item.getFieldName())));
return;
}
LOG.debug("Item: {} is a normal form field", normalizeSpace(item.getName()));
List<String> values;
String fieldName = item.getFieldName();
@@ -105,16 +100,6 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
protected void processFileField(DiskFileItem item) {
if (isExcluded(item.getName())) {
LOG.warn(() -> "File name [%s] is not accepted".formatted(normalizeSpace(item.getName())));
return;
}
if (isExcluded(item.getFieldName())) {
LOG.warn(() -> "Field name [%s] is not accepted".formatted(normalizeSpace(item.getFieldName())));
return;
}
// Skip file uploads that don't have a file name - meaning that no file was selected.
if (item.getName() == null || item.getName().trim().isEmpty()) {
LOG.debug(() -> "No file has been uploaded for the field: " + normalizeSpace(item.getFieldName()));
@@ -116,11 +116,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
String fieldName = fileItemInput.getFieldName();
String fieldValue = readStream(fileItemInput.getInputStream());
if (isExcluded(fieldName)) {
LOG.warn(() -> "Form field [%s] is rejected!".formatted(normalizeSpace(fieldName)));
return;
}
if (exceedsMaxStringLength(fieldName, fieldValue)) {
return;
}
@@ -198,11 +193,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
return;
}
if (isExcluded(fileItemInput.getName())) {
LOG.warn(() -> "File field [%s] rejected".formatted(normalizeSpace(fileItemInput.getName())));
return;
}
if (exceedsMaxFiles(fileItemInput)) {
return;
}
@@ -242,9 +232,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
InputStream input = fileItemInput.getInputStream();
try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
byte[] buffer = new byte[bufferSize];
if (LOG.isDebugEnabled()) {
LOG.debug("Streaming file: {} using buffer size: {}", normalizeSpace(fileItemInput.getName()), bufferSize);
}
LOG.debug("Streaming file: {} using buffer size: {}", normalizeSpace(fileItemInput.getName()), bufferSize);
for (int length; ((length = input.read(buffer)) > 0); ) {
output.write(buffer, 0, length);
}
@@ -18,13 +18,13 @@
*/
package org.apache.struts2.ognl;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ProxyUtil;
import ognl.MemberAccess;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ProxyUtil;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -38,6 +38,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import static java.text.MessageFormat.format;
import static java.util.Collections.emptySet;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
import static org.apache.struts2.util.ConfigParseUtil.toClassObjectsSet;
import static org.apache.struts2.util.ConfigParseUtil.toClassesSet;
import static org.apache.struts2.util.ConfigParseUtil.toNewClassesSet;
@@ -45,10 +49,6 @@ import static org.apache.struts2.util.ConfigParseUtil.toNewPackageNamesSet;
import static org.apache.struts2.util.ConfigParseUtil.toNewPatternsSet;
import static org.apache.struts2.util.ConfigParseUtil.toPackageNamesSet;
import static org.apache.struts2.util.DebugUtils.logWarningForFirstOccurrence;
import static java.text.MessageFormat.format;
import static java.util.Collections.emptySet;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
/**
* Allows access decisions to be made on the basis of whether a member is static or not.
@@ -141,6 +141,9 @@ public class SecurityMemberAccess implements MemberAccess {
public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
LOG.debug("Checking access for [target: {}, member: {}, property: {}]", target, member, propertyName);
if (member == null) {
throw new IllegalArgumentException("Member cannot be null!");
}
if (target != null) {
// Special case: Target is a Class object but not Class.class
if (Class.class.equals(target.getClass()) && !Class.class.equals(target)) {
@@ -209,7 +212,7 @@ public class SecurityMemberAccess implements MemberAccess {
return true;
}
if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
if (!disallowProxyObjectAccess && ProxyUtil.isProxy(target)) {
// If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
// classes/members. This allows the allowlist capability to continue working and offer some level of
// protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
@@ -18,12 +18,12 @@
*/
package org.apache.struts2.util;
import org.apache.struts2.ognl.DefaultOgnlCacheFactory;
import org.apache.struts2.ognl.OgnlCache;
import org.apache.struts2.ognl.OgnlCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.struts2.ognl.DefaultOgnlCacheFactory;
import org.apache.struts2.ognl.OgnlCache;
import org.apache.struts2.ognl.OgnlCacheFactory;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
@@ -81,6 +81,7 @@ public class ProxyUtil {
* @param object the object to check
*/
public static boolean isProxy(Object object) {
if (object == null) return false;
Class<?> clazz = object.getClass();
Boolean flag = isProxyCache.get(clazz);
if (flag != null) {
@@ -121,7 +122,7 @@ public class ProxyUtil {
*/
public static boolean isHibernateProxy(Object object) {
try {
return HibernateProxy.class.isAssignableFrom(object.getClass());
return object != null && HibernateProxy.class.isAssignableFrom(object.getClass());
} catch (NoClassDefFoundError ignored) {
return false;
}
@@ -18,18 +18,6 @@
*/
package org.apache.struts2.dispatcher;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.struts2.StrutsStatics;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -41,6 +29,22 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.jsp.PageContext;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class AttributeMapTest {
@@ -361,4 +365,21 @@ public class AttributeMapTest {
assertEquals("value", value);
}
}
@Test
public void get_whenPageContextHasNoRequest() {
PageContext pageContext = mock(PageContext.class);
when(pageContext.getRequest()).thenReturn(null);
var req = new MockHttpServletRequest();
req.setAttribute("attr", "reqValue");
var attributeMap = new AttributeMap(Map.of(
StrutsStatics.PAGE_CONTEXT, pageContext,
DispatcherConstants.REQUEST, new RequestMap(req)
));
assertEquals("reqValue", attributeMap.get("attr"));
verify(pageContext, never()).findAttribute(anyString());
}
}
@@ -19,13 +19,7 @@
package org.apache.struts2.dispatcher.multipart;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
import org.apache.struts2.config.Configuration;
import org.apache.struts2.config.ConfigurationManager;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.LocalizedMessage;
import org.apache.struts2.inject.Container;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.apache.struts2.views.jsp.StrutsMockServletContext;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.After;
import org.junit.Before;
@@ -37,7 +31,6 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -54,7 +47,6 @@ abstract class AbstractMultiPartRequestTest {
protected final String endline = "\r\n";
protected AbstractMultiPartRequest multiPart;
protected Container container;
abstract protected AbstractMultiPartRequest createMultipartRequest();
@@ -67,13 +59,7 @@ abstract class AbstractMultiPartRequestTest {
}
@Before
public void before() throws Exception {
StrutsMockServletContext servletContext = new StrutsMockServletContext();
Dispatcher dispatcher = StrutsTestCaseHelper.initDispatcher(servletContext, Collections.emptyMap());
ConfigurationManager configurationManager = dispatcher.getConfigurationManager();
Configuration configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
public void before() {
mockRequest = new MockHttpServletRequest();
mockRequest.setCharacterEncoding(StandardCharsets.UTF_8.name());
mockRequest.setMethod("post");
@@ -506,47 +492,6 @@ abstract class AbstractMultiPartRequestTest {
.containsExactly("struts.messages.upload.error.FileUploadException");
}
@Test
public void maliciousFields() throws IOException {
String content = formFile("file1", "test1.csv", "1,2,3,4") +
formField("top.param", "expression") +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
multiPart.parse(mockRequest, tempDir);
assertThat(multiPart.getErrors())
.isEmpty();
assertThat(multiPart.getParameterNames().asIterator()).toIterable()
.isEmpty();
}
@Test
public void maliciousFilename() throws IOException {
String content = formFile("file1", "../test1.csv", "1,2,3,4") +
formField("param", "expression") +
endline + "--" + boundary + "--";
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
multiPart.parse(mockRequest, tempDir);
assertThat(multiPart.getErrors())
.isEmpty();
assertThat(multiPart.getParameterNames().asIterator()).toIterable()
.hasSize(1);
assertThat(multiPart.getParameterNames().asIterator()).toIterable()
.containsOnly("param");
assertThat(multiPart.getFileNames("file1")).isEmpty();
}
protected String formFile(String fieldName, String filename, String content) {
return endline +
"--" + boundary + endline +
@@ -514,71 +514,11 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
}
public void testUnacceptedFieldName() throws Exception {
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
request.setMethod("post");
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"top.file\"; filename=\"deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext()
.withServletRequest(createMultipartRequestMaxSize(2000));
interceptor.intercept(mai);
assertFalse(action.hasActionErrors());
assertNull(action.getUploadFiles());
}
public void testUnacceptedFileName() throws Exception {
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
request.setMethod("post");
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
// inspired by the unit tests for jakarta commons fileupload
String content = ("-----1234\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"../deleteme.txt\"\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"Unit test of ActionFileUploadInterceptor" +
"\r\n" +
"-----1234--\r\n");
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
MockActionInvocation mai = new MockActionInvocation();
mai.setAction(action);
mai.setResultCode("success");
mai.setInvocationContext(ActionContext.getContext());
ActionContext.getContext()
.withServletRequest(createMultipartRequestMaxSize(2000));
interceptor.intercept(mai);
assertFalse(action.hasActionErrors());
assertNull(action.getUploadFiles());
}
private String encodeTextFile(String filename, String contentType, String content) {
return endline +
"--" + boundary +
endline +
"Content-Disposition: form-data; name=\"" + "file" + "\"; filename=\"" + filename + "\"" +
"Content-Disposition: form-data; name=\"" + "file" + "\"; filename=\"" + filename +
endline +
"Content-Type: " + contentType +
endline +
@@ -18,17 +18,26 @@
*/
package org.apache.struts2.ognl;
import ognl.OgnlException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.struts2.SimpleAction;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.TestBean;
import org.apache.struts2.text.TextProvider;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.DefaultPropertiesProvider;
import org.apache.struts2.conversion.impl.ConversionData;
import org.apache.struts2.conversion.impl.XWorkConverter;
import org.apache.struts2.inject.ContainerBuilder;
import org.apache.struts2.ognl.accessor.RootAccessor;
import org.apache.struts2.test.StubConfigurationProvider;
import org.apache.struts2.test.TestBean2;
import org.apache.struts2.text.TextProvider;
import org.apache.struts2.util.Bar;
import org.apache.struts2.util.BarJunior;
import org.apache.struts2.util.Cat;
@@ -37,15 +46,6 @@ import org.apache.struts2.util.Foo;
import org.apache.struts2.util.ValueStackFactory;
import org.apache.struts2.util.location.LocatableProperties;
import org.apache.struts2.util.reflection.ReflectionContextState;
import ognl.OgnlException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.config.DefaultPropertiesProvider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -1234,6 +1234,33 @@ public class OgnlValueStackTest extends XWorkTestCase {
assertNull("accessed private field (result not null) ?", accessedValue);
}
public void testFindValueWithConstructorAndProxyChecks() {
loadButSet(Map.of(
StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Boolean.TRUE.toString(),
StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Boolean.TRUE.toString()));
refreshContainerFields();
String value = "test";
String ognlResult = (String) vs.findValue(
"new org.apache.struts2.ognl.OgnlValueStackTest$ValueHolder('" + value + "').value", String.class);
assertEquals(value, ognlResult);
}
@SuppressWarnings({"unused", "ClassCanBeRecord"})
public static class ValueHolder {
// See testFindValueWithConstructorAndProxyChecks
private final String value;
public ValueHolder(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
static class BadJavaBean {
private int count;
private int count2;
@@ -93,7 +93,7 @@ public class DefaultExcludedPatternsCheckerTest extends XWorkTestCase {
public void testDefaultExcludePatterns() throws Exception {
// given
List<String> prefixes = Arrays.asList("#[0].%s", "[0].%s", "top.%s", "%{[0].%s}", "%{#[0].%s}", "%{top.%s}", "%{#top.%s}", "%{#%s}", "%{%s}", "#%s", "top.param", "%{top.request}", "#top.param");
List<String> prefixes = Arrays.asList("#[0].%s", "[0].%s", "top.%s", "%{[0].%s}", "%{#[0].%s}", "%{top.%s}", "%{#top.%s}", "%{#%s}", "%{%s}", "#%s");
List<String> inners = Arrays.asList("servletRequest", "servletResponse", "servletContext", "application", "session", "struts", "request", "response", "dojo", "parameters");
List<String> suffixes = Arrays.asList("['test']", "[\"test\"]", ".test");
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jakarta</artifactId>
<packaging>pom</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-tools-view-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-async-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-cdi-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-config-browser-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-convention-plugin</artifactId>
@@ -18,26 +18,27 @@
*/
package org.apache.struts2.convention;
import org.apache.struts2.action.Action;
import org.apache.struts2.ActionContext;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.finder.ClassLoaderInterface;
import org.apache.struts2.util.finder.ClassLoaderInterfaceDelegate;
import org.apache.struts2.util.finder.ResourceFinder;
import org.apache.struts2.util.finder.Test;
import jakarta.servlet.ServletContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionContext;
import org.apache.struts2.action.Action;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.TextParseUtil;
import org.apache.struts2.util.finder.ClassLoaderInterface;
import org.apache.struts2.util.finder.ClassLoaderInterfaceDelegate;
import org.apache.struts2.util.finder.ResourceFinder;
import org.apache.struts2.util.finder.Test;
import java.io.IOException;
import java.net.URL;
@@ -123,23 +124,24 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
/**
* Constructs the SimpleResultMapBuilder using the given result location.
*
* @param servletContext The ServletContext for finding the resources of the web application.
* @param container The Xwork container
* @param relativeResultTypes The list of result types that can have locations that are relative
* and the result location (which is the resultPath plus the namespace) prepended to them.
* @param servletContext The ServletContext for finding the resources of the web application.
* @param container The Xwork container
* @param relativeResultTypes The list of result types that can have locations that are relative
* and the result location (which is the resultPath plus the namespace) prepended to them.
*/
@Inject
public DefaultResultMapBuilder(ServletContext servletContext, Container container,
@Inject(ConventionConstants.CONVENTION_RELATIVE_RESULT_TYPES) String relativeResultTypes) {
@Inject(ConventionConstants.CONVENTION_RELATIVE_RESULT_TYPES) String relativeResultTypes) {
this.servletContext = servletContext;
this.relativeResultTypes = Set.of(relativeResultTypes.split("\\s*[,]\\s*"));
this.conventionsService = container.getInstance(ConventionsService.class, container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE));
this.relativeResultTypes = TextParseUtil.commaDelimitedStringToSet(relativeResultTypes);
String instanceName = container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE);
this.conventionsService = container.getInstance(ConventionsService.class, instanceName);
}
/**
* @param flatResultLayout If 'true' result resources will be expected to be in the form
* ${namespace}/${actionName}-${result}.${extension}, otherwise in the form
* ${namespace}/${actionName}/${result}.${extension}
* ${namespace}/${actionName}-${result}.${extension}, otherwise in the form
* ${namespace}/${actionName}/${result}.${extension}
*/
@Inject(ConventionConstants.CONVENTION_RESULT_FLAT_LAYOUT)
public void setFlatResultLayout(String flatResultLayout) {
@@ -150,8 +152,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* {@inheritDoc}
*/
public Map<String, ResultConfig> build(Class<?> actionClass,
org.apache.struts2.convention.annotation.Action annotation, String actionName,
PackageConfig packageConfig) {
org.apache.struts2.convention.annotation.Action annotation, String actionName,
PackageConfig packageConfig) {
// Get the default result location from the annotation or configuration
String defaultResultPath = conventionsService.determineResultPath(actionClass);
@@ -164,7 +166,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
// Check for resources with the action name
final String namespace = packageConfig.getNamespace();
if (namespace != null && namespace.startsWith("/")) {
defaultResultPath = defaultResultPath + namespace.substring(1);
defaultResultPath = defaultResultPath + namespace.substring(1);
} else if (namespace != null) {
defaultResultPath = defaultResultPath + namespace;
}
@@ -184,7 +186,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
Map<String, ResultConfig> results = new HashMap<>();
Map<String, ResultTypeConfig> resultsByExtension = conventionsService.getResultTypesByExtension(packageConfig);
createFromResources(actionClass, results, defaultResultPath, resultPrefix, actionName,
packageConfig, resultsByExtension);
packageConfig, resultsByExtension);
//get inherited @Results and @Result (class level)
for (Class<?> clazz : ReflectionTools.getClassHierarchy(actionClass)) {
@@ -202,11 +204,12 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
/**
* Creates results from @Results and @Result annotations
* @param actionClass class to check for annotations
* @param packageConfig packageConfig where the action will be located
* @param defaultResultPath default result path
* @param results map of results
* @param resultsByExtension map of result types keyed by extension
*
* @param actionClass class to check for annotations
* @param packageConfig packageConfig where the action will be located
* @param defaultResultPath default result path
* @param results map of results
* @param resultsByExtension map of result types keyed by extension
*/
protected void createResultsFromAnnotations(Class<?> actionClass, PackageConfig packageConfig, String defaultResultPath,
Map<String, ResultConfig> results, Map<String, ResultTypeConfig> resultsByExtension) {
@@ -228,24 +231,23 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* Creates any result types from the resources available in the web application. This scans the
* web application resources using the servlet context.
*
* @param actionClass The action class the results are being built for.
* @param results The results map to put the result configs created into.
* @param resultPath The calculated path to the resources.
* @param resultPrefix The prefix for the result. This is usually <code>/resultPath/actionName</code>.
* @param actionName The action name which is used only for logging in this implementation.
* @param packageConfig The package configuration which is passed along in order to determine
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param actionClass The action class the results are being built for.
* @param results The results map to put the result configs created into.
* @param resultPath The calculated path to the resources.
* @param resultPrefix The prefix for the result. This is usually <code>/resultPath/actionName</code>.
* @param actionName The action name which is used only for logging in this implementation.
* @param packageConfig The package configuration which is passed along in order to determine
* @param resultsByExtension The map of extensions to result type configuration instances.
*/
protected void createFromResources(Class<?> actionClass, Map<String, ResultConfig> results,
final String resultPath, final String resultPrefix, final String actionName,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) {
final String resultPath, final String resultPrefix, final String actionName,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) {
if (LOG.isTraceEnabled()) {
LOG.trace("Searching for results in the Servlet container at [{}]" +
" with result prefix of [#1]", resultPath, resultPrefix);
LOG.trace("Searching for results in the Servlet container at [{}] with result prefix of [{}]",
resultPath, resultPrefix);
}
// Build from web application using the ServletContext
@SuppressWarnings("unchecked")
Set<String> paths = servletContext.getResourcePaths(flatResultLayout ? resultPath : resultPrefix);
if (paths != null) {
for (String path : paths) {
@@ -255,13 +257,12 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
if (StringUtils.isBlank(fileName) || StringUtils.startsWith(fileName, ".")) {
LOG.trace("Ignoring file without name [{}]", path);
continue;
}
else if(fileName.lastIndexOf(".") > 0){
String suffix = fileName.substring(fileName.lastIndexOf(".")+1);
} else if (fileName.lastIndexOf(".") > 0) {
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
if(conventionsService.getResultTypesByExtension(packageConfig).get(suffix) == null) {
if (conventionsService.getResultTypesByExtension(packageConfig).get(suffix) == null) {
LOG.debug("No result type defined for file suffix : [{}]. Ignoring file {}", suffix, fileName);
continue;
continue;
}
}
@@ -270,11 +271,10 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
// Building from the classpath
String classPathLocation = resultPath.startsWith("/") ?
resultPath.substring(1, resultPath.length()) : resultPath;
String classPathLocation = resultPath.startsWith("/") ? resultPath.substring(1) : resultPath;
if (LOG.isTraceEnabled()) {
LOG.trace("Searching for results in the class path at [{}]"
+ " with a result prefix of [{}] and action name [{}]", classPathLocation, resultPrefix,
+ " with a result prefix of [{}] and action name [{}]", classPathLocation, resultPrefix,
actionName);
}
@@ -296,7 +296,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
}
} catch (IOException ex) {
LOG.error("Unable to scan directory [{}] for results", ex, classPathLocation);
LOG.error("Unable to scan directory [{}] for results", classPathLocation, ex);
}
}
@@ -320,30 +320,28 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
private Test<URL> getResourceTest(final String resultPath, final String actionName) {
return new Test<URL>() {
public boolean test(URL url) {
String urlStr = url.toString();
int index = urlStr.lastIndexOf(resultPath);
String path = urlStr.substring(index + resultPath.length());
return path.startsWith(actionName);
}
return url -> {
String urlStr = url.toString();
int index = urlStr.lastIndexOf(resultPath);
String path = urlStr.substring(index + resultPath.length());
return path.startsWith(actionName);
};
}
/**
* Makes all the results for the given path.
*
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param resultPrefix The is the result prefix which is the result location plus the action name.
* This is used to determine if the path contains a result code or not.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param resultPrefix The is the result prefix which is the result location plus the action name.
* This is used to determine if the path contains a result code or not.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
*/
protected void makeResults(Class<?> actionClass, String path, String resultPrefix,
Map<String, ResultConfig> results, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultConfig> results, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
if (path.startsWith(resultPrefix)) {
int indexOfDot = path.indexOf('.', resultPrefix.length());
@@ -351,26 +349,27 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
// This case is when the path doesn't contain a result code
if (indexOfDot == resultPrefix.length()) {
if (LOG.isTraceEnabled()) {
LOG.trace("The result file [{}] has no result code and therefore" +
" will be associated with success, input and error by default. This might" +
" be overridden by another result file or an annotation.", path);
LOG.trace("""
The result file [{}] has no result code and therefore
will be associated with success, input and error by default.
This might be overridden by another result file or an annotation.
""".stripIndent(), path);
}
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.SUCCESS);
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.INPUT);
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.ERROR);
// This case is when the path contains a result code
// This case is when the path contains a result code
} else if (indexOfDot > resultPrefix.length()) {
if (LOG.isTraceEnabled()) {
LOG.trace("The result file [{}] has a result code and therefore" +
" will be associated with only that result code.", path);
LOG.trace("The result file [{}] has a result code and therefore will be associated with only that result code.", path);
}
String resultCode = path.substring(resultPrefix.length() + 1, indexOfDot);
ResultConfig result = createResultConfig(actionClass,
new ResultInfo(resultCode, path, packageConfig, resultsByExtension),
packageConfig, null);
new ResultInfo(resultCode, path, packageConfig, resultsByExtension),
packageConfig, null);
results.put(resultCode, result);
}
}
@@ -380,16 +379,16 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* Checks if result was already assigned, if not checks global results first and if exists, adds reference to it.
* If not, creates package specific result.
*
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param resultKey The result name to use
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param resultKey The result name to use
*/
protected void addResult(Class<?> actionClass, String path, Map<String, ResultConfig> results,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension,
String resultKey) {
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension,
String resultKey) {
if (!results.containsKey(resultKey)) {
Map<String, ResultConfig> globalResults = packageConfig.getAllGlobalResults();
@@ -405,8 +404,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
protected void createFromAnnotations(Map<String, ResultConfig> resultConfigs,
String resultPath, PackageConfig packageConfig, Result[] results,
Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) {
String resultPath, PackageConfig packageConfig, Result[] results,
Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) {
// Check for multiple results on the class
for (Result result : results) {
for (String name : result.name()) {
@@ -425,25 +424,23 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* information from the annotation and anything that isn't specified will be fetched from the
* PackageConfig defaults (if they exist).
*
* @param actionClass The action class the results are being built for.
* @param info The result info that is used to create the ResultConfig instance.
* @param packageConfig The PackageConfig to use to fetch defaults for result and parameters.
* @param result (Optional) The result annotation to pull additional information from.
* @return The ResultConfig or null if the Result annotation is given and the annotation is
* targeted to some other action than this one.
* @param actionClass The action class the results are being built for.
* @param info The result info that is used to create the ResultConfig instance.
* @param packageConfig The PackageConfig to use to fetch defaults for result and parameters.
* @param result (Optional) The result annotation to pull additional information from.
* @return The ResultConfig or null if the Result annotation is given and the annotation is
* targeted to some other action than this one.
*/
@SuppressWarnings(value = {"unchecked"})
protected ResultConfig createResultConfig(Class<?> actionClass, ResultInfo info,
PackageConfig packageConfig, Result result) {
protected ResultConfig createResultConfig(Class<?> actionClass, ResultInfo info, PackageConfig packageConfig, Result result) {
// Look up by the type that was determined from the annotation or by the extension in the
// ResultInfo class
ResultTypeConfig resultTypeConfig = packageConfig.getAllResultTypeConfigs().get(info.type);
if (resultTypeConfig == null) {
throw new ConfigurationException("The Result type [" + info.type + "] which is" +
" defined in the Result annotation on the class [" + actionClass + "] or determined" +
" by the file extension or is the default result type for the PackageConfig of the" +
" action, could not be found as a result-type defined for the Struts/XWork package [" +
packageConfig.getName() + "]");
" defined in the Result annotation on the class [" + actionClass + "] or determined" +
" by the file extension or is the default result type for the PackageConfig of the" +
" action, could not be found as a result-type defined for the Struts/XWork package [" +
packageConfig.getName() + "]");
}
// Add the default parameters for the result type config (if any)
@@ -474,15 +471,15 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
public final String type;
public ResultInfo(String name, String location, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultTypeConfig> resultsByExtension) {
this.name = name;
this.location = location;
this.type = determineType(location, packageConfig, resultsByExtension);
}
public ResultInfo(String name, Result result, PackageConfig packageConfig,
String resultPath, Class<?> actionClass,
Map<String, ResultTypeConfig> resultsByExtension) {
String resultPath, Class<?> actionClass,
Map<String, ResultTypeConfig> resultsByExtension) {
this.name = name;
if (StringUtils.isNotBlank(result.type())) {
this.type = result.type();
@@ -490,8 +487,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
this.type = determineType(result.location(), packageConfig, resultsByExtension);
} else {
throw new ConfigurationException("The action class [" + actionClass + "] contains a " +
"result annotation that has no type parameter and no location parameter. One of " +
"these must be defined.");
"result annotation that has no type parameter and no location parameter. One of " +
"these must be defined.");
}
// See if we can handle relative locations or not
@@ -507,7 +504,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
String determineType(String location, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultTypeConfig> resultsByExtension) {
int indexOfDot = location.lastIndexOf(".");
if (indexOfDot > 0) {
String extension = location.substring(indexOfDot + 1);
@@ -516,7 +513,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
return resultTypeConfig.getName();
} else
throw new ConfigurationException("Unable to find a result type for extension [" + extension + "] " +
"in location attribute [" + location + "].");
"in location attribute [" + location + "].");
} else {
return packageConfig.getFullDefaultResultType();
}
@@ -18,36 +18,48 @@
*/
package org.apache.struts2.convention;
import jakarta.servlet.ServletContext;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.inject.Container;
import junit.framework.TestCase;
import org.apache.struts2.convention.actions.NoAnnotationAction;
import org.apache.struts2.convention.actions.result.*;
import org.apache.struts2.convention.actions.result.ActionLevelResultAction;
import org.apache.struts2.convention.actions.result.ActionLevelResultsAction;
import org.apache.struts2.convention.actions.result.ActionLevelResultsNamesAction;
import org.apache.struts2.convention.actions.result.ClassLevelResultAction;
import org.apache.struts2.convention.actions.result.ClassLevelResultsAction;
import org.apache.struts2.convention.actions.result.GlobalResultAction;
import org.apache.struts2.convention.actions.result.GlobalResultOverrideAction;
import org.apache.struts2.convention.actions.result.InheritedResultExtends;
import org.apache.struts2.convention.actions.result.InheritedResultsExtends;
import org.apache.struts2.convention.actions.result.OverrideInheritedResultExtends;
import org.apache.struts2.convention.actions.result.OverrideResultAction;
import org.apache.struts2.convention.actions.resultpath.ClassLevelResultPathAction;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.inject.Container;
import org.apache.struts2.result.ServletDispatcherResult;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Test;
import jakarta.servlet.ServletContext;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.apache.struts2.convention.ReflectionTools.getAnnotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* <p>
* This class tests the simple result map builder.
* </p>
*/
public class DefaultResultMapBuilderTest extends TestCase {
public class DefaultResultMapBuilderTest {
private Container container;
private ConventionsService conventionsService;
public void testBuild() throws Exception {
@Test
public void testBuild() {
ServletContext context = mockServletContext("/WEB-INF/location");
// Test with a slash
@@ -66,7 +78,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
verify(context, "/WEB-INF/location", results, false);
}
public void testResultOverrride() throws Exception {
@Test
public void testResultOverride() throws Exception {
ServletContext context = mockServletContext("/WEB-INF/location");
// Test with a slash
@@ -80,7 +93,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals("/WEB-INF/location/namespace/error-overriden.jsp", result.getParams().get("location"));
}
public void testGlobalResult() throws Exception {
@Test
public void testGlobalResult() {
ServletContext context = mockServletContext("/WEB-INF/location");
this.conventionsService = new ConventionsServiceImpl("/WEB-INF/location");
@@ -105,8 +119,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals("/globalError.jsp", result.getParams().get("location"));
}
public void testGlobalResultOverride() throws Exception {
@Test
public void testGlobalResultOverride() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
String resultPath = "/WEB-INF/location";
// Setup some mock jsps
@@ -139,7 +153,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals(resultPath + "/namespace/action-error.jsp", result.getParams().get("location"));
}
public void testNull() throws Exception {
@Test
public void testNull() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(null);
EasyMock.replay(context);
@@ -153,7 +168,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testResultPath() throws Exception {
@Test
public void testResultPath() {
ServletContext context = mockServletContext("/class-level");
// Test with a result path
@@ -164,7 +180,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
verify(context, "/class-level", results, false);
}
public void testFromServletContextWithBadNames() throws Exception {
@Test
public void testFromServletContextWithBadNames() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -184,7 +201,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testFromServletContext() throws Exception {
@Test
public void testFromServletContext() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -218,7 +236,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testFromServletContextNotFlat() throws Exception {
@Test
public void testFromServletContextNotFlat() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -254,7 +273,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testIgnoreFilesWithoutName() throws Exception {
@Test
public void testIgnoreFilesWithoutName() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -278,7 +298,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testClassLevelSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -301,7 +322,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelInheritedSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelInheritedSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -324,7 +346,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelOverwriteInheritedSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelOverwriteInheritedSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -349,7 +372,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelMultipleResultAnnotation() throws Exception {
@Test
public void testClassLevelMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -388,7 +412,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelInheritanceMultipleResultAnnotation() throws Exception {
@Test
public void testClassLevelInheritanceMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -427,7 +452,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelSingleResultAnnotation() throws Exception {
@Test
public void testActionLevelSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -450,7 +476,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultAnnotation() throws Exception {
@Test
public void testActionLevelMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -485,7 +512,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultNamesAnnotation() throws Exception {
@Test
public void testActionLevelMultipleResultNamesAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -520,7 +548,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultNamesAnnotationNoName() throws Exception {
@Test
public void testActionLevelMultipleResultNamesAnnotationNoName() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -543,7 +572,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassPath() throws Exception {
@Test
public void testClassPath() {
ServletContext context = EasyMock.createNiceMock(ServletContext.class);
ResultTypeConfig resultType = new ResultTypeConfig.Builder("freemarker", "org.apache.struts2.result.ServletDispatcherResult").
@@ -600,8 +630,7 @@ public class DefaultResultMapBuilderTest extends TestCase {
return context;
}
private void verify(ServletContext context, String resultPath, Map<String, ResultConfig> results,
boolean redirect) {
private void verify(ServletContext context, String resultPath, Map<String, ResultConfig> results, boolean redirect) {
assertEquals(4, results.size());
assertEquals("success", results.get("success").getName());
assertEquals("input", results.get("input").getName());
@@ -633,16 +662,13 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setup() {
this.container = EasyMock.createNiceMock(Container.class);
EasyMock.expect(container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE)).andReturn("convention").anyTimes();
EasyMock.expect(container.getInstance(ConventionsService.class, "convention")).andAnswer(new IAnswer<ConventionsService>() {
public ConventionsService answer() throws Throwable {
return DefaultResultMapBuilderTest.this.conventionsService;
}
}).anyTimes();
EasyMock.expect(container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE))
.andReturn("convention").anyTimes();
EasyMock.expect(container.getInstance(ConventionsService.class, "convention"))
.andAnswer(() -> DefaultResultMapBuilderTest.this.conventionsService).anyTimes();
EasyMock.replay(this.container);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jasperreports-plugin</artifactId>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-javatemplates-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jfreechart-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-json-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-junit-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-plugins</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-rest-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-spring-plugin</artifactId>
@@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
@@ -87,4 +88,91 @@ public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectProxyMember, ""));
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
}
@Test
public void nullTargetAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullTargetAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullTargetAndTargetAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullMemberAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullMemberAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullMemberAndTargetNotAllowedAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetNotAllowedAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullPropertyName() {
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
Object action = proxy.getAction();
assertTrue(sma.isAccessible(context, action, proxyObjectProxyMember, null));
}
}
@@ -23,9 +23,9 @@ import org.apache.struts2.SimpleAction;
import org.apache.struts2.TestBean;
import org.apache.struts2.TestSubBean;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.config.providers.XmlConfigurationProvider;
import org.apache.struts2.util.ProxyUtil;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.springframework.context.ApplicationContext;
/**
@@ -46,6 +46,8 @@ public class SpringProxyUtilTest extends XWorkTestCase {
}
public void testIsProxy() throws Exception {
assertFalse(ProxyUtil.isProxy(null));
Object simpleAction = appContext.getBean("simple-action");
assertFalse(ProxyUtil.isProxy(simpleAction));
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-testng-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-tiles-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-xslt-plugin</artifactId>
+4 -4
View File
@@ -29,7 +29,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>struts2-parent</artifactId>
<version>7.0.1</version>
<version>7.0.3</version>
<packaging>pom</packaging>
<name>Struts 2</name>
<url>https://struts.apache.org/</url>
@@ -51,7 +51,7 @@
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
<tag>STRUTS_7_0_1</tag>
<tag>STRUTS_7_0_3</tag>
</scm>
<issueManagement>
@@ -104,7 +104,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2025-02-02T07:23:42Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2025-02-17T09:39:05Z</project.build.outputTimestamp>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
@@ -807,7 +807,7 @@
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>4.2.0</version>
<version>4.9.0</version>
<scope>test</scope>
</dependency>