mirror of
https://github.com/apache/struts.git
synced 2026-08-08 16:16:58 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80225c7169 | |||
| 6b2de2df4a | |||
| 15e257d075 | |||
| daf2f51721 | |||
| 440dfe8c6d | |||
| a016d50b53 | |||
| d718290ad1 | |||
| 2f73fc9ccf | |||
| d054d638b6 | |||
| 399ec92c4a | |||
| c58fe8bfb9 | |||
| aa56c07972 | |||
| 5148a9f088 | |||
| f743b18e19 | |||
| b674e32cbe | |||
| 862013273b | |||
| d92509fc29 | |||
| e11ea4778f | |||
| 0649b5686b | |||
| 272797f764 | |||
| 03cbbdba90 | |||
| 1460c482c3 | |||
| ea97930474 | |||
| f25321661f | |||
| a92568423e | |||
| 036c4ddb74 | |||
| 831568929c | |||
| 84a51cce0e | |||
| 0f61944d64 | |||
| ca4e07cead | |||
| 8fb9aba74b | |||
| c45664a330 | |||
| 91c9314176 | |||
| a4e9902d94 | |||
| fe5b197738 | |||
| 87e322dbe2 | |||
| 7909f43237 | |||
| 1fe822cca0 | |||
| ef90b3ba3c | |||
| d35ec15c76 | |||
| 583b174fe6 | |||
| 9b044377ac | |||
| 58f37babbf | |||
| fe3dd6ba9f | |||
| 7a77c7a82c |
@@ -17,16 +17,18 @@ github:
|
||||
protected_branches:
|
||||
main:
|
||||
# contexts are the names of checks that must pass.
|
||||
contexts:
|
||||
- build
|
||||
required_status_checks:
|
||||
contexts:
|
||||
- "Build and Test (JDK 17)"
|
||||
required_pull_request_reviews:
|
||||
# it does not work because our github teams are private/secret, see INFRA-25666
|
||||
require_code_owner_reviews: false
|
||||
required_approving_review_count: 0
|
||||
release/*:
|
||||
# contexts are the names of checks that must pass.
|
||||
contexts:
|
||||
- build
|
||||
required_status_checks:
|
||||
contexts:
|
||||
- "Build and Test (JDK 8)"
|
||||
required_pull_request_reviews:
|
||||
# it does not work because our github teams are private/secret, see INFRA-25666
|
||||
require_code_owner_reviews: false
|
||||
|
||||
@@ -17,7 +17,8 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
branches:
|
||||
- 'release/*'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
@@ -41,15 +42,21 @@ jobs:
|
||||
matrix:
|
||||
language: [ 'java' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.27.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3.27.1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.27.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
cache: 'maven'
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.28.8
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3.28.8
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.28.8
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
+7
-4
@@ -1,5 +1,5 @@
|
||||
# IDEA
|
||||
.idea
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
@@ -38,11 +38,14 @@ buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
plugins/testng/test-output
|
||||
test-output
|
||||
plugins/testng/test-output/
|
||||
test-output/
|
||||
|
||||
# Sonar
|
||||
/.sonar/
|
||||
.sonar/
|
||||
|
||||
# Tidelift CLI scanner
|
||||
.tidelift
|
||||
|
||||
# Claude Code specific local settings
|
||||
.claude/
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Apache Struts 2 Framework (Version 6.7.x)
|
||||
|
||||
This is the Apache Struts 2 web framework, a free open-source solution for creating Java web applications. The codebase is a multi-module Maven project with comprehensive plugin architecture.
|
||||
|
||||
## Build System & Common Commands
|
||||
|
||||
### Maven Commands
|
||||
```bash
|
||||
# Build entire project
|
||||
./mvnw clean install
|
||||
|
||||
# Build without running tests
|
||||
./mvnw clean install -DskipTests
|
||||
|
||||
# Build without assembly
|
||||
./mvnw clean install -DskipAssembly
|
||||
|
||||
# Run all tests
|
||||
./mvnw clean test
|
||||
|
||||
# Run tests with coverage
|
||||
./mvnw clean verify -Pcoverage -DskipAssembly
|
||||
|
||||
# Run integration tests
|
||||
./mvnw clean verify -DskipAssembly
|
||||
|
||||
# Test specific module
|
||||
./mvnw -pl core clean test
|
||||
./mvnw -pl plugins/spring clean test
|
||||
|
||||
# Check for security vulnerabilities
|
||||
./mvnw clean verify -Pdependency-check
|
||||
|
||||
# Run Apache RAT license check
|
||||
./mvnw clean prepare-package
|
||||
```
|
||||
|
||||
### Test Framework
|
||||
- **Primary**: JUnit 4.13.2 with Maven Surefire Plugin 3.5.1
|
||||
- **Pattern**: `**/*Test.java` (excludes `**/TestBean.java`)
|
||||
- **Coverage**: JaCoCo 0.8.12
|
||||
- **Additional**: Mockito, EasyMock, AssertJ, Spring Test
|
||||
- **Integration**: Maven Failsafe Plugin with Jetty on port 8090
|
||||
|
||||
## Project Architecture
|
||||
|
||||
### Core Structure
|
||||
```
|
||||
struts6/
|
||||
├── core/ # Core Struts 2 framework (main dependency)
|
||||
├── plugins/ # Plugin modules
|
||||
│ ├── spring/ # Spring integration
|
||||
│ ├── json/ # JSON support
|
||||
│ ├── tiles/ # Apache Tiles integration
|
||||
│ ├── velocity/ # Velocity template engine
|
||||
│ └── [20+ other plugins]
|
||||
├── apps/ # Sample applications
|
||||
│ ├── showcase/ # Feature demonstration app
|
||||
│ └── rest-showcase/ # REST API examples
|
||||
├── bundles/ # OSGi bundles
|
||||
├── bom/ # Bill of Materials
|
||||
└── assembly/ # Distribution packaging
|
||||
```
|
||||
|
||||
### Key Technologies
|
||||
- **Java**: Minimum JDK 8, supports up to JDK 21
|
||||
- **Servlet API**: 3.1+ required
|
||||
- **Expression Language**: OGNL 3.3.5
|
||||
- **Dependency Injection**: Custom container (`com.opensymphony.xwork2.inject`)
|
||||
- **Templating**: FreeMarker 2.3.33 (default), Velocity, JSP
|
||||
- **Logging**: SLF4J 2.0.16 with Log4j2 2.24.1
|
||||
- **Build**: Maven with wrapper (3.9.6)
|
||||
|
||||
### Core Components Architecture
|
||||
|
||||
#### Action Framework (MVC Pattern)
|
||||
- **Actions**: Located in `core/src/main/java/org/apache/struts2/action/`
|
||||
- **Action Support**: `ActionSupport` base class with validation and i18n
|
||||
- **Action Context**: `ActionContext` provides access to servlet objects
|
||||
- **Action Invocation**: `DefaultActionInvocation` handles action execution
|
||||
|
||||
#### Configuration System
|
||||
- **XML-based**: Primary configuration via `struts.xml` files
|
||||
- **Annotation-based**: Convention plugin for zero-config approach
|
||||
- **Java-based**: `StrutsJavaConfiguration` for programmatic setup
|
||||
- **Property files**: `struts.properties` for framework settings
|
||||
|
||||
#### Interceptor Chain
|
||||
- **Framework Core**: All requests processed through interceptor chain
|
||||
- **Built-in Interceptors**: 20+ interceptors in `org.apache.struts2.interceptor`
|
||||
- **Validation**: `ValidationInterceptor` with annotation support
|
||||
- **File Upload**: `FileUploadInterceptor` with security controls
|
||||
- **Parameters**: `ParametersInterceptor` with OGNL expression handling
|
||||
|
||||
#### Result Framework
|
||||
- **Result Types**: JSP, FreeMarker, Redirect, Stream, JSON, etc.
|
||||
- **Chaining**: `ActionChainResult` for action-to-action calls
|
||||
- **Templates**: Pluggable result renderers
|
||||
|
||||
#### Value Stack (OGNL Integration)
|
||||
- **Expression Language**: OGNL for property access and method calls
|
||||
- **Security**: `SecurityMemberAccess` prevents dangerous operations
|
||||
- **Performance**: Caffeine-based expression caching
|
||||
- **Context**: CompoundRoot provides hierarchical value resolution
|
||||
|
||||
### Plugin Architecture
|
||||
Each plugin is a separate Maven module with:
|
||||
- **Plugin Descriptor**: `struts-plugin.xml` defines beans and configuration
|
||||
- **Dependency Isolation**: Separate classloaders for plugin resources
|
||||
- **Extension Points**: Configurable via dependency injection
|
||||
- **Popular Plugins**: Spring (DI), JSON (REST), Tiles (Layout), Bean Validation (JSR-303)
|
||||
|
||||
### Security Architecture
|
||||
- **OGNL Security**: Restricted method access and class loading
|
||||
- **CSRF Protection**: Token-based with `TokenInterceptor`
|
||||
- **File Upload Security**: Type and size restrictions
|
||||
- **Content Security Policy**: Built-in CSP support
|
||||
- **Input Validation**: Server-side validation framework
|
||||
- **Pattern Matching**: Configurable allowed/excluded patterns
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Code Organization
|
||||
- **Package Structure**: Follow existing `org.apache.struts2.*` hierarchy
|
||||
- **Naming Conventions**: Use Struts conventions (Actions end with `Action`)
|
||||
- **Configuration**: Prefer XML configuration in `struts.xml` for complex setups
|
||||
- **Testing**: Each module has comprehensive unit and integration tests
|
||||
|
||||
### Plugin Development
|
||||
```java
|
||||
// Plugin descriptor example (struts-plugin.xml)
|
||||
<bean type="com.opensymphony.xwork2.ObjectFactory"
|
||||
name="myObjectFactory"
|
||||
class="com.example.MyObjectFactory" />
|
||||
```
|
||||
|
||||
### Common Patterns
|
||||
- **Action Implementation**: Extend `ActionSupport` or implement `Action`
|
||||
- **Result Mapping**: Use result configuration in `struts.xml`
|
||||
- **Interceptor Development**: Extend `AbstractInterceptor`
|
||||
- **Type Conversion**: Implement `TypeConverter` for custom types
|
||||
- **Validation**: Use validation XML or annotations
|
||||
|
||||
### Important Notes
|
||||
- **Version**: Currently 6.7.5-SNAPSHOT (release branch: `release/struts-6-7-x`)
|
||||
- **Java Compatibility**: Compiled for Java 8, tested through Java 21
|
||||
- **Security**: Always validate inputs and follow OWASP guidelines
|
||||
- **Performance**: Leverage built-in caching (OGNL expressions, templates)
|
||||
- **Deprecation**: Some legacy XWork components marked for removal
|
||||
|
||||
### Build Profiles
|
||||
- **coverage**: Enables JaCoCo coverage reporting
|
||||
- **dependency-check**: OWASP dependency vulnerability scanning
|
||||
- **jdk17**: Special configuration for Java 17+ module system
|
||||
|
||||
This is a mature, enterprise-grade framework with extensive documentation at https://struts.apache.org/ and active community support through Apache mailing lists and JIRA (project WW).
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
<name>Struts 2 Rest Showcase Webapp</name>
|
||||
<description>Struts 2 Rest Showcase Example</description>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-assembly</artifactId>
|
||||
|
||||
+4
-4
@@ -25,11 +25,11 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-bom</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Struts 2 Bill of Materials</name>
|
||||
@@ -44,7 +44,7 @@
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<struts-version.version>6.7.2</struts-version.version>
|
||||
<struts-version.version>6.8.0</struts-version.version>
|
||||
<maven.site.skip>true</maven.site.skip>
|
||||
<maven.site.deploy.skip>true</maven.site.deploy.skip>
|
||||
</properties>
|
||||
@@ -190,7 +190,7 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<tag>STRUTS_6_7_2</tag>
|
||||
<tag>STRUTS_6_8_0</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>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-admin-bundle</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-demo-bundle</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+5
-1
@@ -68,6 +68,8 @@ import com.opensymphony.xwork2.validator.ValidatorFileParser;
|
||||
import ognl.PropertyAccessor;
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
import org.apache.struts2.dispatcher.Parameter;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceReader;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
import org.apache.struts2.interceptor.exec.ExecutorProvider;
|
||||
import org.apache.struts2.interceptor.exec.StrutsExecutorProvider;
|
||||
import org.apache.struts2.url.QueryStringBuilder;
|
||||
@@ -159,7 +161,9 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
|
||||
.factory(UrlEncoder.class, StrutsUrlEncoder.class, Scope.SINGLETON)
|
||||
.factory(UrlDecoder.class, StrutsUrlDecoder.class, Scope.SINGLETON)
|
||||
|
||||
.factory(ExecutorProvider.class, StrutsExecutorProvider.class, Scope.SINGLETON);
|
||||
.factory(ExecutorProvider.class, StrutsExecutorProvider.class, Scope.SINGLETON)
|
||||
|
||||
.factory(CspNonceReader.class, StrutsCspNonceReader.class, Scope.SINGLETON);
|
||||
|
||||
for (Map.Entry<String, Object> entry : DefaultConfiguration.BOOTSTRAP_CONSTANTS.entrySet()) {
|
||||
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
package com.opensymphony.xwork2.factory;
|
||||
|
||||
import com.opensymphony.xwork2.ObjectFactory;
|
||||
import com.opensymphony.xwork2.conversion.TypeConverter;
|
||||
import com.opensymphony.xwork2.inject.Container;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -33,16 +33,15 @@ public class StrutsConverterFactory implements ConverterFactory {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(StrutsConverterFactory.class);
|
||||
|
||||
private Container container;
|
||||
private ObjectFactory objectFactory;
|
||||
|
||||
@Inject
|
||||
public void setContainer(Container container) {
|
||||
this.container = container;
|
||||
public void setObjectFactory(ObjectFactory objectFactory) {
|
||||
this.objectFactory = objectFactory;
|
||||
}
|
||||
|
||||
public TypeConverter buildConverter(Class<? extends TypeConverter> converterClass, Map<String, Object> extraContext) throws Exception {
|
||||
LOG.debug("Creating converter of type [{}]", converterClass.getCanonicalName());
|
||||
return container.inject(converterClass);
|
||||
return (TypeConverter)objectFactory.buildBean(converterClass, extraContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import ognl.Ognl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @deprecated since 6.8.0, to be removed, see {@link ReflectionContextFactory}
|
||||
*/
|
||||
@Deprecated
|
||||
public class OgnlReflectionContextFactory implements ReflectionContextFactory {
|
||||
|
||||
public Map createDefaultContext(Object root) {
|
||||
|
||||
@@ -160,6 +160,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)) {
|
||||
@@ -228,7 +231,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+4
@@ -20,6 +20,10 @@ package com.opensymphony.xwork2.util.reflection;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @deprecated since 6.8.0, avoid using this interface and any of its implementation, it's going to be removed soon
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ReflectionContextFactory {
|
||||
/**
|
||||
* Creates and returns a new standard naming context for evaluating an OGNL
|
||||
|
||||
@@ -509,4 +509,11 @@ public final class StrutsConstants {
|
||||
|
||||
/** See {@link org.apache.struts2.interceptor.exec.ExecutorProvider} */
|
||||
public static final String STRUTS_EXECUTOR_PROVIDER = "struts.executor.provider";
|
||||
|
||||
/**
|
||||
* See {@link org.apache.struts2.interceptor.csp.CspNonceReader}
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public static final String STRUTS_CSP_NONCE_READER = "struts.csp.nonce.reader";
|
||||
public static final String STRUTS_CSP_NONCE_SOURCE = "struts.csp.nonce.source";
|
||||
}
|
||||
|
||||
@@ -50,17 +50,16 @@ import javax.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 +94,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;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.apache.struts2.components.template.TemplateEngineManager;
|
||||
import org.apache.struts2.components.template.TemplateRenderingContext;
|
||||
import org.apache.struts2.dispatcher.AttributeMap;
|
||||
import org.apache.struts2.dispatcher.StaticContentLoader;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceReader;
|
||||
import org.apache.struts2.util.ComponentUtils;
|
||||
import org.apache.struts2.util.TextProviderHelper;
|
||||
import org.apache.struts2.views.annotations.StrutsTagAttribute;
|
||||
@@ -40,7 +41,6 @@ import org.apache.struts2.views.util.ContextUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -528,6 +528,8 @@ public abstract class UIBean extends Component {
|
||||
|
||||
protected TemplateEngineManager templateEngineManager;
|
||||
|
||||
protected CspNonceReader cspNonceReader;
|
||||
|
||||
@Inject(StrutsConstants.STRUTS_UI_TEMPLATEDIR)
|
||||
public void setDefaultTemplateDir(String dir) {
|
||||
this.defaultTemplateDir = dir;
|
||||
@@ -553,6 +555,11 @@ public abstract class UIBean extends Component {
|
||||
this.templateEngineManager = mgr;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setCspNonceReader(CspNonceReader cspNonceReader) {
|
||||
this.cspNonceReader = cspNonceReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean end(Writer writer, String body) {
|
||||
evaluateParams();
|
||||
@@ -886,13 +893,12 @@ public abstract class UIBean extends Component {
|
||||
}
|
||||
|
||||
// to be used with the CSP interceptor - adds the nonce value as a parameter to be accessed from ftl files
|
||||
HttpSession session = stack.getActionContext().getServletRequest().getSession(false);
|
||||
Object nonceValue = session != null ? session.getAttribute("nonce") : null;
|
||||
CspNonceReader.NonceValue nonceValue = cspNonceReader.readNonceValue(stack);
|
||||
|
||||
if (nonceValue != null) {
|
||||
addParameter("nonce", nonceValue.toString());
|
||||
if (nonceValue.isNonceValueSet()) {
|
||||
addParameter("nonce", nonceValue.getNonceValue());
|
||||
} else {
|
||||
LOG.debug("Session is not active, cannot obtain nonce value");
|
||||
LOG.debug("Nonce not defined in: {}", nonceValue.getSource());
|
||||
}
|
||||
|
||||
evaluateExtraParams();
|
||||
|
||||
@@ -69,6 +69,7 @@ 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.interceptor.csp.CspNonceReader;
|
||||
import org.apache.struts2.interceptor.exec.ExecutorProvider;
|
||||
import org.apache.struts2.ognl.OgnlGuard;
|
||||
import org.apache.struts2.url.QueryStringBuilder;
|
||||
@@ -450,6 +451,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
|
||||
|
||||
alias(ExecutorProvider.class, StrutsConstants.STRUTS_EXECUTOR_PROVIDER, builder, props, Scope.SINGLETON);
|
||||
|
||||
alias(CspNonceReader.class, StrutsConstants.STRUTS_CSP_NONCE_READER, builder, props, Scope.SINGLETON);
|
||||
|
||||
switchDevMode(props);
|
||||
}
|
||||
|
||||
|
||||
+6
-39
@@ -20,9 +20,7 @@ package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import com.opensymphony.xwork2.LocaleProviderFactory;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
@@ -41,16 +39,13 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(AbstractMultiPartRequest.class);
|
||||
|
||||
private static final String EXCLUDED_FILE_PATTERN = "^(.*[<>&\"'|;\\\\/?*:]+.*|.*\\.\\..*)$";
|
||||
private static final String EXCLUDED_FILE_PATTERN_WITH_DMI_SUPPORT = "^(?!action:[^<>&\"'|;\\\\/?*:]+(![^<>&\"'|;\\\\/?*:]+)?$)(.*[<>&\"'|;\\\\/?*:]+.*|.*\\.\\..*)$\n";
|
||||
|
||||
/**
|
||||
* Defines the internal buffer size used during streaming operations.
|
||||
*/
|
||||
public static final int BUFFER_SIZE = 10240;
|
||||
|
||||
/**
|
||||
* Internal list of raised errors to be passed to the Struts2 framework.
|
||||
* Internal list of raised errors to be passed to the the Struts2 framework.
|
||||
*/
|
||||
protected List<LocalizedMessage> errors = new ArrayList<>();
|
||||
|
||||
@@ -86,17 +81,6 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
*/
|
||||
protected Locale defaultLocale = Locale.ENGLISH;
|
||||
|
||||
private final ExcludedPatternsChecker patternsChecker;
|
||||
|
||||
protected AbstractMultiPartRequest(String dmiValue) {
|
||||
patternsChecker = new DefaultExcludedPatternsChecker();
|
||||
if (BooleanUtils.toBoolean(dmiValue)) {
|
||||
((DefaultExcludedPatternsChecker) patternsChecker).setAdditionalExcludePatterns(EXCLUDED_FILE_PATTERN_WITH_DMI_SUPPORT);
|
||||
} else {
|
||||
((DefaultExcludedPatternsChecker) patternsChecker).setAdditionalExcludePatterns(EXCLUDED_FILE_PATTERN);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bufferSize Sets the buffer size to be used.
|
||||
*/
|
||||
@@ -140,7 +124,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
|
||||
/**
|
||||
* @param request Inspect the servlet request and set the locale if one wasn't provided by
|
||||
* the Struts2 framework.
|
||||
* the Struts2 framework.
|
||||
*/
|
||||
protected void setLocale(HttpServletRequest request) {
|
||||
if (defaultLocale == null) {
|
||||
@@ -151,7 +135,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
/**
|
||||
* Build error message.
|
||||
*
|
||||
* @param e the Throwable/Exception
|
||||
* @param e the Throwable/Exception
|
||||
* @param args arguments
|
||||
* @return error message
|
||||
*/
|
||||
@@ -164,7 +148,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
|
||||
*/
|
||||
*/
|
||||
public List<LocalizedMessage> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
@@ -174,24 +158,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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileName file name to check
|
||||
* @return true if the file name is excluded
|
||||
*/
|
||||
protected boolean isExcluded(String fileName) {
|
||||
return patternsChecker.isExcluded(fileName).isExcluded();
|
||||
return FilenameUtils.getName(originalFileName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+34
-33
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import org.apache.commons.fileupload.FileCountLimitExceededException;
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.commons.fileupload.FileUploadBase;
|
||||
@@ -30,7 +29,6 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
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.dispatcher.LocalizedMessage;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -44,7 +42,6 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
|
||||
|
||||
@@ -60,14 +57,9 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
|
||||
// maps parameter name -> List of param values
|
||||
protected Map<String, List<String>> params = new HashMap<>();
|
||||
public JakartaMultiPartRequest() {
|
||||
super(Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false)
|
||||
public JakartaMultiPartRequest(String dmiValue) {
|
||||
super(dmiValue);
|
||||
}
|
||||
// List to track all FileItem instances for comprehensive cleanup
|
||||
protected List<FileItem> allFileItems = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's
|
||||
@@ -113,6 +105,10 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
if (ServletFileUpload.isMultipartContent(request)) {
|
||||
for (FileItem item : parseRequest(request, saveDir)) {
|
||||
LOG.debug("Found file item: [{}]", normalizeSpace(item.getFieldName()));
|
||||
|
||||
// Track all FileItem instances for comprehensive cleanup
|
||||
allFileItems.add(item);
|
||||
|
||||
if (item.isFormField()) {
|
||||
processNormalFormField(item, request.getCharacterEncoding());
|
||||
} else {
|
||||
@@ -125,16 +121,6 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
protected void processFileField(FileItem item) {
|
||||
LOG.debug("Item is a file upload");
|
||||
|
||||
if (isExcluded(item.getName())) {
|
||||
LOG.warn("File name [{}] is not accepted", normalizeSpace(item.getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isExcluded(item.getFieldName())) {
|
||||
LOG.warn("Field name [{}] is not accepted", 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()));
|
||||
@@ -156,11 +142,6 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
try {
|
||||
LOG.debug("Item is a normal form field");
|
||||
|
||||
if (isExcluded(item.getFieldName())) {
|
||||
LOG.warn("Form field name [{}] is not accepted", normalizeSpace(item.getFieldName()));
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> values;
|
||||
if (params.get(item.getFieldName()) != null) {
|
||||
values = params.get(item.getFieldName());
|
||||
@@ -265,7 +246,11 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
// Ensure file exists even if it is empty.
|
||||
if (diskFileItem.getSize() == 0 && storeLocation != null && !storeLocation.exists()) {
|
||||
try {
|
||||
storeLocation.createNewFile();
|
||||
if (storeLocation.createNewFile()) {
|
||||
LOG.debug("File {} has been created", storeLocation.getAbsolutePath());
|
||||
} else {
|
||||
LOG.warn("File {} already exists", storeLocation.getAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Cannot write uploaded empty file to disk: {}", storeLocation.getAbsolutePath(), e);
|
||||
}
|
||||
@@ -382,15 +367,31 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
|
||||
*/
|
||||
public void cleanUp() {
|
||||
Set<String> names = files.keySet();
|
||||
for (String name : names) {
|
||||
List<FileItem> items = files.get(name);
|
||||
for (FileItem item : items) {
|
||||
LOG.debug("Removing file [{}]", normalizeSpace(name));
|
||||
if (!item.isInMemory()) {
|
||||
item.delete();
|
||||
try {
|
||||
LOG.debug("Performing comprehensive cleanup for {} file items.", allFileItems.size());
|
||||
for (FileItem item : allFileItems) {
|
||||
try {
|
||||
if (item instanceof DiskFileItem) {
|
||||
DiskFileItem diskItem = (DiskFileItem) item;
|
||||
File storeLocation = diskItem.getStoreLocation();
|
||||
if (storeLocation != null && storeLocation.exists()) {
|
||||
LOG.debug("Deleting temporary file: [{}]", storeLocation.getName());
|
||||
if (!storeLocation.delete()) {
|
||||
LOG.warn("Unable to delete temporary file: [{}]", storeLocation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also call the item's delete method as backup
|
||||
if (!item.isInMemory()) {
|
||||
item.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Error during cleanup of file item: [{}]", normalizeSpace(item.getFieldName()), e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Clear only the tracking collection, preserve parsed data
|
||||
allFileItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-23
@@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import org.apache.commons.fileupload.FileItemIterator;
|
||||
import org.apache.commons.fileupload.FileItemStream;
|
||||
import org.apache.commons.fileupload.FileUploadBase;
|
||||
@@ -28,7 +27,6 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
import org.apache.commons.fileupload.util.Streams;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -208,15 +206,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
}
|
||||
}
|
||||
|
||||
public JakartaStreamMultiPartRequest() {
|
||||
super(Boolean.FALSE.toString());
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false)
|
||||
public JakartaStreamMultiPartRequest(String dmiValue) {
|
||||
super(dmiValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the upload.
|
||||
*
|
||||
@@ -325,10 +314,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
*/
|
||||
protected void processFileItemStreamAsFormField(FileItemStream itemStream) {
|
||||
String fieldName = itemStream.getFieldName();
|
||||
if (isExcluded(fieldName)) {
|
||||
LOG.warn("Form field [{}] rejected!", normalizeSpace(fieldName));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
List<String> values;
|
||||
String fieldValue = Streams.asString(itemStream.openStream());
|
||||
@@ -357,11 +342,6 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isExcluded(itemStream.getName())) {
|
||||
LOG.warn("File field [{}] rejected", normalizeSpace(itemStream.getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
File file = null;
|
||||
try {
|
||||
// Create the temporary upload file.
|
||||
@@ -407,9 +387,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
}
|
||||
|
||||
File file = File.createTempFile(prefix + "_", suffix, new File(location));
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Creating temporary file [{}] (originally [{}]).", file.getName(), normalizeSpace(fileName));
|
||||
}
|
||||
LOG.debug("Creating temporary file [{}] (originally [{}]).", file.getName(), normalizeSpace(fileName));
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ public abstract class AbstractFileUploadInterceptor extends AbstractInterceptor
|
||||
}
|
||||
|
||||
// If it's null the upload failed
|
||||
if (file == null) {
|
||||
if (file == null || file.getContent() == null) {
|
||||
String errMsg = getTextMessage(action, STRUTS_MESSAGES_ERROR_UPLOADING_KEY, new String[]{inputName});
|
||||
if (validation != null) {
|
||||
validation.addFieldError(inputName, errMsg);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.interceptor.csp;
|
||||
|
||||
import org.apache.struts2.util.ValueStack;
|
||||
|
||||
/**
|
||||
* Reads the nonce value using the ValueStack, {@link StrutsCspNonceReader} is the default implementation
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public interface CspNonceReader {
|
||||
|
||||
NonceValue readNonceValue(ValueStack stack);
|
||||
|
||||
class NonceValue {
|
||||
private final String nonceValue;
|
||||
private final CspNonceSource source;
|
||||
|
||||
private NonceValue(String nonceValue, CspNonceSource source) {
|
||||
this.nonceValue = nonceValue;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public static NonceValue ofSession(String nonceValue) {
|
||||
return new NonceValue(nonceValue, CspNonceSource.SESSION);
|
||||
}
|
||||
|
||||
public static NonceValue ofRequest(String nonceValue) {
|
||||
return new NonceValue(nonceValue, CspNonceSource.REQUEST);
|
||||
}
|
||||
|
||||
public static NonceValue ofNullSession() {
|
||||
return new NonceValue(null, CspNonceSource.SESSION);
|
||||
}
|
||||
|
||||
public static NonceValue ofNullRequest() {
|
||||
return new NonceValue(null, CspNonceSource.REQUEST);
|
||||
}
|
||||
|
||||
public boolean isNonceValueSet() {
|
||||
return nonceValue != null;
|
||||
}
|
||||
|
||||
public String getNonceValue() {
|
||||
return nonceValue;
|
||||
}
|
||||
|
||||
public CspNonceSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String displayNonce;
|
||||
if (nonceValue != null && nonceValue.length() >= 4) {
|
||||
displayNonce = String.format("nonceValue='%s**********'", nonceValue.substring(0, 4));
|
||||
} else if (nonceValue != null) {
|
||||
displayNonce = String.format("nonceValue='%s**********'", nonceValue);
|
||||
} else {
|
||||
displayNonce = "nonceValue='<null>'";
|
||||
}
|
||||
return "NonceValue{" +
|
||||
displayNonce +
|
||||
", source=" + source +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.interceptor.csp;
|
||||
|
||||
/**
|
||||
* Source of the nonce value
|
||||
*/
|
||||
public enum CspNonceSource {
|
||||
REQUEST,
|
||||
SESSION
|
||||
}
|
||||
@@ -45,11 +45,10 @@ public interface CspSettings {
|
||||
String CSP_REPORT_TYPE = "application/csp-report";
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #addCspHeaders(HttpServletRequest, HttpServletResponse)} instead
|
||||
* Adds CSP related headers to response based on request state (e.g., if session has been created)
|
||||
*
|
||||
* @since Struts 6.0.3
|
||||
*/
|
||||
@Deprecated
|
||||
void addCspHeaders(HttpServletResponse response);
|
||||
|
||||
void addCspHeaders(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,15 +18,17 @@
|
||||
*/
|
||||
package org.apache.struts2.interceptor.csp;
|
||||
|
||||
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 org.apache.struts2.action.CspSettingsAware;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@@ -43,68 +45,102 @@ import static java.lang.String.format;
|
||||
*/
|
||||
public class DefaultCspSettings implements CspSettings {
|
||||
|
||||
private final static Logger LOG = LogManager.getLogger(DefaultCspSettings.class);
|
||||
private static final Logger LOG = LogManager.getLogger(DefaultCspSettings.class);
|
||||
private static final String NONCE_KEY = "nonce";
|
||||
|
||||
private final SecureRandom sRand = new SecureRandom();
|
||||
|
||||
private CspNonceSource nonceSource = CspNonceSource.SESSION;
|
||||
|
||||
protected String reportUri;
|
||||
protected String reportTo;
|
||||
// default to reporting mode
|
||||
protected String cspHeader = CSP_REPORT_HEADER;
|
||||
|
||||
@Override
|
||||
public void addCspHeaders(HttpServletResponse response) {
|
||||
throw new UnsupportedOperationException("Unsupported implementation, use #addCspHeaders(HttpServletRequest request, HttpServletResponse response)");
|
||||
@Inject(value = StrutsConstants.STRUTS_CSP_NONCE_SOURCE, required = false)
|
||||
public void setNonceSource(String nonceSource) {
|
||||
if (StringUtils.isBlank(nonceSource)) {
|
||||
this.nonceSource = CspNonceSource.SESSION;
|
||||
} else {
|
||||
this.nonceSource = CspNonceSource.valueOf(nonceSource.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCspHeaders(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (this.nonceSource == CspNonceSource.SESSION) {
|
||||
addCspHeadersWithSession(request, response);
|
||||
} else if (this.nonceSource == CspNonceSource.REQUEST) {
|
||||
addCspHeadersWithRequest(request, response);
|
||||
} else {
|
||||
LOG.warn("Unknown nonce source: {}, ignoring CSP settings", nonceSource);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCspHeadersWithSession(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (isSessionActive(request)) {
|
||||
LOG.trace("Session is active, applying CSP settings");
|
||||
associateNonceWithSession(request);
|
||||
response.setHeader(cspHeader, createPolicyFormat(request));
|
||||
String nonceValue = generateNonceValue();
|
||||
request.getSession().setAttribute(NONCE_KEY, nonceValue);
|
||||
response.setHeader(cspHeader, createPolicyFormat(nonceValue));
|
||||
} else {
|
||||
LOG.trace("Session is not active, ignoring CSP settings");
|
||||
LOG.debug("Session is not active, ignoring CSP settings");
|
||||
}
|
||||
}
|
||||
|
||||
private void addCspHeadersWithRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
String nonceValue = generateNonceValue();
|
||||
request.setAttribute(NONCE_KEY, nonceValue);
|
||||
response.setHeader(cspHeader, createPolicyFormat(nonceValue));
|
||||
}
|
||||
|
||||
private boolean isSessionActive(HttpServletRequest request) {
|
||||
return request.getSession(false) != null;
|
||||
}
|
||||
|
||||
private void associateNonceWithSession(HttpServletRequest request) {
|
||||
String nonceValue = Base64.getUrlEncoder().encodeToString(getRandomBytes());
|
||||
request.getSession().setAttribute("nonce", nonceValue);
|
||||
private String generateNonceValue() {
|
||||
return Base64.getUrlEncoder().encodeToString(getRandomBytes());
|
||||
}
|
||||
|
||||
protected String createPolicyFormat(HttpServletRequest request) {
|
||||
StringBuilder policyFormatBuilder = new StringBuilder()
|
||||
.append(OBJECT_SRC)
|
||||
.append(format(" '%s'; ", NONE))
|
||||
.append(SCRIPT_SRC)
|
||||
.append(" 'nonce-%s' ") // nonce placeholder
|
||||
.append(format("'%s' ", STRICT_DYNAMIC))
|
||||
.append(format("%s %s; ", HTTP, HTTPS))
|
||||
.append(BASE_URI)
|
||||
.append(format(" '%s'; ", NONE));
|
||||
protected String createPolicyFormat(String nonceValue) {
|
||||
StringBuilder builder = new StringBuilder()
|
||||
.append(OBJECT_SRC)
|
||||
.append(format(" '%s'; ", NONE))
|
||||
.append(SCRIPT_SRC)
|
||||
.append(format(" 'nonce-%s' ", nonceValue))
|
||||
.append(format("'%s' ", STRICT_DYNAMIC))
|
||||
.append(format("%s %s; ", HTTP, HTTPS))
|
||||
.append(BASE_URI)
|
||||
.append(format(" '%s'; ", NONE));
|
||||
|
||||
if (reportUri != null) {
|
||||
policyFormatBuilder
|
||||
.append(REPORT_URI)
|
||||
.append(format(" %s; ", reportUri));
|
||||
if(reportTo != null) {
|
||||
policyFormatBuilder
|
||||
builder
|
||||
.append(REPORT_URI)
|
||||
.append(format(" %s; ", reportUri));
|
||||
if (reportTo != null) {
|
||||
builder
|
||||
.append(REPORT_TO)
|
||||
.append(format(" %s; ", reportTo));
|
||||
}
|
||||
}
|
||||
|
||||
return format(policyFormatBuilder.toString(), getNonceString(request));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 6.8.0, for removal
|
||||
*/
|
||||
@Deprecated
|
||||
protected String createPolicyFormat(HttpServletRequest request) {
|
||||
throw new UnsupportedOperationException("Unsupported implementation, use createPolicyFormat(String) instead!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 6.8.0, for removal
|
||||
*/
|
||||
@Deprecated
|
||||
protected String getNonceString(HttpServletRequest request) {
|
||||
Object nonce = request.getSession().getAttribute("nonce");
|
||||
return Objects.toString(nonce);
|
||||
throw new UnsupportedOperationException("Unsupported implementation, don't use!");
|
||||
}
|
||||
|
||||
private byte[] getRandomBytes() {
|
||||
@@ -133,10 +169,10 @@ public class DefaultCspSettings implements CspSettings {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultCspSettings{" +
|
||||
"reportUri='" + reportUri + '\'' +
|
||||
", reportTo='" + reportTo + '\'' +
|
||||
", cspHeader='" + cspHeader + '\'' +
|
||||
'}';
|
||||
"reportUri='" + reportUri + '\'' +
|
||||
", reportTo='" + reportTo + '\'' +
|
||||
", cspHeader='" + cspHeader + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.interceptor.csp;
|
||||
|
||||
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 org.apache.struts2.util.ValueStack;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/**
|
||||
* Reads nonce value from session or request attribute.
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public class StrutsCspNonceReader implements CspNonceReader {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(StrutsCspNonceReader.class);
|
||||
|
||||
private final CspNonceSource nonceSource;
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_CSP_NONCE_SOURCE, required = false)
|
||||
public StrutsCspNonceReader(String source) {
|
||||
if (StringUtils.isBlank(source)) {
|
||||
this.nonceSource = CspNonceSource.SESSION;
|
||||
} else {
|
||||
this.nonceSource = CspNonceSource.valueOf(source.toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NonceValue readNonceValue(ValueStack stack) {
|
||||
HttpServletRequest request = stack.getActionContext().getServletRequest();
|
||||
NonceValue nonceValue;
|
||||
|
||||
if (nonceSource == CspNonceSource.SESSION) {
|
||||
LOG.debug("Reading nonce value from session");
|
||||
nonceValue = readNonceFromSession(request);
|
||||
} else if (nonceSource == CspNonceSource.REQUEST) {
|
||||
LOG.debug("Reading nonce value from request attribute");
|
||||
nonceValue = readNonceFromRequest(request);
|
||||
} else {
|
||||
LOG.warn("Unknown nonce source: {}, reading nonce value from session", nonceSource);
|
||||
nonceValue = readNonceFromSession(request);
|
||||
}
|
||||
return nonceValue;
|
||||
}
|
||||
|
||||
private NonceValue readNonceFromSession(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
Object nonceValue = session != null ? session.getAttribute("nonce") : null;
|
||||
if (nonceValue == null) {
|
||||
LOG.debug("Session is not active, cannot obtain nonce value");
|
||||
return NonceValue.ofNullSession();
|
||||
}
|
||||
return NonceValue.ofSession(nonceValue.toString());
|
||||
}
|
||||
|
||||
private NonceValue readNonceFromRequest(HttpServletRequest request) {
|
||||
Object nonceValue = request.getAttribute("nonce");
|
||||
if (nonceValue == null) {
|
||||
LOG.warn("Request attribute 'nonce' is not set, cannot obtain nonce value");
|
||||
return NonceValue.ofNullRequest();
|
||||
}
|
||||
|
||||
return NonceValue.ofRequest(nonceValue.toString());
|
||||
}
|
||||
}
|
||||
@@ -280,4 +280,7 @@ struts.url.queryStringParser=strutsQueryStringParser
|
||||
struts.url.encoder=strutsUrlEncoder
|
||||
struts.url.decoder=strutsUrlDecoder
|
||||
|
||||
### Defines source to read nonce value from, possible values are: request, session
|
||||
struts.csp.nonceSource=session
|
||||
|
||||
### END SNIPPET: complete_file
|
||||
|
||||
@@ -265,4 +265,7 @@
|
||||
<bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" name="struts"
|
||||
class="org.apache.struts2.interceptor.exec.StrutsExecutorProvider"/>
|
||||
|
||||
<bean type="org.apache.struts2.interceptor.csp.CspNonceReader" name="struts"
|
||||
class="org.apache.struts2.interceptor.csp.StrutsCspNonceReader"/>
|
||||
|
||||
</struts>
|
||||
|
||||
@@ -165,14 +165,6 @@
|
||||
<td class="tag-attribute">String</td>
|
||||
<td class="tag-attribute">Set the value used to retrieve the option value.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxLength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute"></td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute">Integer</td>
|
||||
<td class="tag-attribute">Deprecated. Use maxlength instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxlength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
|
||||
@@ -117,14 +117,6 @@
|
||||
<td class="tag-attribute">String</td>
|
||||
<td class="tag-attribute">String that will be appended to the label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxLength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute"></td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute">Integer</td>
|
||||
<td class="tag-attribute">Deprecated. Use maxlength instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxlength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
|
||||
@@ -117,14 +117,6 @@
|
||||
<td class="tag-attribute">String</td>
|
||||
<td class="tag-attribute">String that will be appended to the label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxLength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute"></td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute">Integer</td>
|
||||
<td class="tag-attribute">Deprecated. Use maxlength instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tag-attribute">maxlength</td>
|
||||
<td class="tag-attribute">false</td>
|
||||
|
||||
@@ -1233,6 +1233,34 @@ public class OgnlValueStackTest extends XWorkTestCase {
|
||||
assertNull("accessed private field (result not null) ?", accessedValue);
|
||||
}
|
||||
|
||||
public void testFindValueWithConstructorAndProxyChecks() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Boolean.TRUE.toString());
|
||||
properties.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Boolean.TRUE.toString());
|
||||
loadButSet(properties);
|
||||
refreshContainerFields();
|
||||
|
||||
String value = "test";
|
||||
String ognlResult = (String) vs.findValue(
|
||||
"new com.opensymphony.xwork2.ognl.OgnlValueStackTest$ValueHolder('" + value + "').value", String.class);
|
||||
|
||||
assertEquals(value, ognlResult);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
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;
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.apache.struts2.components;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.components.template.Template;
|
||||
import org.apache.struts2.components.template.TemplateEngine;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsCheckerTest.NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER;
|
||||
@@ -236,6 +238,8 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
ActionContext.getContext().withServletRequest(req);
|
||||
|
||||
TextField txtFld = new TextField(stack, req, res);
|
||||
container.inject(txtFld);
|
||||
|
||||
txtFld.setAccesskey(accesskeyValue);
|
||||
txtFld.evaluateParams();
|
||||
|
||||
@@ -250,6 +254,8 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
ActionContext.getContext().withServletRequest(req);
|
||||
|
||||
TextField txtFld = new TextField(stack, req, res);
|
||||
container.inject(txtFld);
|
||||
|
||||
txtFld.addParameter("value", value);
|
||||
txtFld.evaluateParams();
|
||||
|
||||
@@ -338,6 +344,8 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
ActionContext.getContext().withServletRequest(req);
|
||||
|
||||
TextField txtFld = new TextField(stack, req, res);
|
||||
container.inject(txtFld);
|
||||
|
||||
txtFld.setCssClass(cssClass);
|
||||
txtFld.evaluateParams();
|
||||
|
||||
@@ -352,6 +360,8 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
ActionContext.getContext().withServletRequest(req);
|
||||
|
||||
TextField txtFld = new TextField(stack, req, res);
|
||||
container.inject(txtFld);
|
||||
|
||||
txtFld.setStyle(cssStyle);
|
||||
txtFld.evaluateParams();
|
||||
|
||||
@@ -372,6 +382,8 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
actionContext.withSession(new SessionMap(req));
|
||||
|
||||
DoubleSelect dblSelect = new DoubleSelect(stack, req, res);
|
||||
container.inject(dblSelect);
|
||||
|
||||
dblSelect.evaluateParams();
|
||||
|
||||
assertEquals(nonceVal, dblSelect.getAttributes().get("nonce"));
|
||||
@@ -392,11 +404,35 @@ public class UIBeanTest extends StrutsInternalTestCase {
|
||||
session.invalidate();
|
||||
|
||||
DoubleSelect dblSelect = new DoubleSelect(stack, req, res);
|
||||
container.inject(dblSelect);
|
||||
|
||||
dblSelect.evaluateParams();
|
||||
|
||||
assertNull(dblSelect.getAttributes().get("nonce"));
|
||||
}
|
||||
|
||||
public void testNonceOfRequestAttribute() {
|
||||
Map<String, String> params = new HashMap<String, String>(){{
|
||||
put(StrutsConstants.STRUTS_CSP_NONCE_SOURCE, "request");
|
||||
}};
|
||||
initDispatcher(params);
|
||||
|
||||
String nonceVal = "r4nd0m";
|
||||
ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setAttribute("nonce", nonceVal);
|
||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||
ActionContext actionContext = stack.getActionContext();
|
||||
actionContext.withServletRequest(req);
|
||||
|
||||
DoubleSelect dblSelect = new DoubleSelect(stack, req, res);
|
||||
container.inject(dblSelect);
|
||||
|
||||
dblSelect.evaluateParams();
|
||||
|
||||
assertEquals(nonceVal, dblSelect.getAttributes().get("nonce"));
|
||||
}
|
||||
|
||||
public void testSetNullUiStaticContentPath() {
|
||||
// given
|
||||
ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.dispatcher.multipart;
|
||||
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test cases for {@link JakartaMultiPartRequest} that verify security-related functionality,
|
||||
* specifically comprehensive cleanup of temporary files.
|
||||
*/
|
||||
public class JakartaMultiPartRequestTest extends StrutsInternalTestCase {
|
||||
|
||||
private File tempDir;
|
||||
private JakartaMultiPartRequest multiPartRequest;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Create a temporary directory for test files
|
||||
Path tempPath = Files.createTempDirectory("struts-multipart-test");
|
||||
tempDir = tempPath.toFile();
|
||||
|
||||
multiPartRequest = new TestableJakartaMultiPartRequest();
|
||||
multiPartRequest.setMaxSize("2048");
|
||||
multiPartRequest.setMaxFiles("10");
|
||||
multiPartRequest.setMaxFileSize("1024");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
if (tempDir != null && tempDir.exists()) {
|
||||
// Clean up test directory
|
||||
File[] files = tempDir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
tempDir.delete();
|
||||
}
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that comprehensive cleanup removes all temporary files created during multipart processing.
|
||||
* This addresses the security vulnerability where temporary files could be leaked.
|
||||
*/
|
||||
public void testComprehensiveCleanupRemovesAllTempFiles() throws Exception {
|
||||
// Create a mock multipart request with both file upload and form field
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setContentType("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
|
||||
request.setMethod("POST");
|
||||
|
||||
String multipartContent = createMultipartContent();
|
||||
request.setContent(multipartContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Count files before processing
|
||||
int filesBefore = countTempFiles();
|
||||
|
||||
// Process the multipart request
|
||||
multiPartRequest.parse(request, tempDir.getAbsolutePath());
|
||||
|
||||
// Count files after processing (should be more due to temp files)
|
||||
int filesAfterProcessing = countTempFiles();
|
||||
|
||||
// Verify that temp files were created during processing
|
||||
assertTrue("Temporary files should be created during multipart processing",
|
||||
filesAfterProcessing > filesBefore);
|
||||
|
||||
// Perform cleanup
|
||||
multiPartRequest.cleanUp();
|
||||
|
||||
// Count files after cleanup
|
||||
int filesAfterCleanup = countTempFiles();
|
||||
|
||||
// Verify comprehensive cleanup removed all temporary files
|
||||
assertEquals("All temporary files should be cleaned up", filesBefore, filesAfterCleanup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all FileItem instances are tracked for cleanup, including both file uploads and form fields.
|
||||
*/
|
||||
public void testAllFileItemsAreTrackedForCleanup() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setContentType("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
|
||||
request.setMethod("POST");
|
||||
|
||||
String multipartContent = createMultipartContentWithMultipleFields();
|
||||
request.setContent(multipartContent.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Process the multipart request
|
||||
multiPartRequest.parse(request, tempDir.getAbsolutePath());
|
||||
|
||||
// Access the tracked items through our testable implementation
|
||||
TestableJakartaMultiPartRequest testable = (TestableJakartaMultiPartRequest) multiPartRequest;
|
||||
List<FileItem> trackedItems = testable.getAllFileItems();
|
||||
|
||||
// Verify that all items (both files and form fields) are tracked
|
||||
assertTrue("Should track multiple FileItem instances", trackedItems.size() >= 3);
|
||||
|
||||
// Verify tracking includes both form fields and file uploads
|
||||
boolean hasFormField = false;
|
||||
boolean hasFileUpload = false;
|
||||
|
||||
for (FileItem item : trackedItems) {
|
||||
if (item.isFormField()) {
|
||||
hasFormField = true;
|
||||
} else {
|
||||
hasFileUpload = true;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("Should track form field items", hasFormField);
|
||||
assertTrue("Should track file upload items", hasFileUpload);
|
||||
}
|
||||
|
||||
private String createMultipartContent() {
|
||||
return "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"textField\"\r\n\r\n" +
|
||||
"test value\r\n" +
|
||||
"------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"fileField\"; filename=\"test.txt\"\r\n" +
|
||||
"Content-Type: text/plain\r\n\r\n" +
|
||||
"file content\r\n" +
|
||||
"------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n";
|
||||
}
|
||||
|
||||
private String createMultipartContentWithMultipleFields() {
|
||||
return "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"textField1\"\r\n\r\n" +
|
||||
"value1\r\n" +
|
||||
"------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"textField2\"\r\n\r\n" +
|
||||
"value2\r\n" +
|
||||
"------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n" +
|
||||
"Content-Disposition: form-data; name=\"fileField\"; filename=\"test.txt\"\r\n" +
|
||||
"Content-Type: text/plain\r\n\r\n" +
|
||||
"file content\r\n" +
|
||||
"------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n";
|
||||
}
|
||||
|
||||
private int countTempFiles() {
|
||||
if (tempDir == null || !tempDir.exists()) {
|
||||
return 0;
|
||||
}
|
||||
File[] files = tempDir.listFiles();
|
||||
return files != null ? files.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Testable subclass that exposes internal state for verification
|
||||
*/
|
||||
private static class TestableJakartaMultiPartRequest extends JakartaMultiPartRequest {
|
||||
public List<FileItem> getAllFileItems() {
|
||||
return allFileItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
-65
@@ -24,9 +24,6 @@ import com.opensymphony.xwork2.DefaultLocaleProvider;
|
||||
import com.opensymphony.xwork2.ValidationAwareSupport;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import com.opensymphony.xwork2.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
@@ -101,6 +98,53 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
};
|
||||
|
||||
public static final UploadedFile NULL_CONTENT = new UploadedFile() {
|
||||
@Override
|
||||
public Long length() {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputName() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private ActionFileUploadInterceptor interceptor;
|
||||
private File tempDir;
|
||||
|
||||
@@ -208,6 +252,22 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertTrue(msg.indexOf("inputName") > 0);
|
||||
}
|
||||
|
||||
public void testAcceptFileWithNoContent() {
|
||||
interceptor.setAllowedTypes("text/plain");
|
||||
|
||||
ValidationAwareSupport validation = new ValidationAwareSupport();
|
||||
boolean notOk = interceptor.acceptFile(validation, NULL_CONTENT, "filename.html", "text/plain", "inputName");
|
||||
|
||||
assertFalse(notOk);
|
||||
assertFalse(validation.getFieldErrors().isEmpty());
|
||||
assertTrue(validation.hasErrors());
|
||||
List<String> errors = validation.getFieldErrors().get("inputName");
|
||||
assertEquals(1, errors.size());
|
||||
String msg = errors.get(0);
|
||||
assertTrue(msg.startsWith("Error uploading:"));
|
||||
assertTrue(msg.indexOf("inputName") > 0);
|
||||
}
|
||||
|
||||
public void testAcceptFileWithMaxSize() throws Exception {
|
||||
interceptor.setMaximumSize(10L);
|
||||
|
||||
@@ -666,68 +726,6 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
|
||||
}
|
||||
|
||||
public void testUnacceptedFieldName() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.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");
|
||||
req.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(req, 2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(action.hasActionErrors());
|
||||
assertNull(action.getUploadFiles());
|
||||
}
|
||||
|
||||
public void testUnacceptedFileName() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.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");
|
||||
req.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(req, 2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(action.hasActionErrors());
|
||||
assertNull(action.getUploadFiles());
|
||||
}
|
||||
|
||||
private String encodeTextFile(String filename, String contentType, String content) {
|
||||
return "\r\n" +
|
||||
"--" +
|
||||
|
||||
@@ -312,7 +312,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
*/
|
||||
public static class CustomDefaultCspSettings extends DefaultCspSettings {
|
||||
|
||||
protected String createPolicyFormat(HttpServletRequest request) {
|
||||
protected String createPolicyFormat(String nonceValue) {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.opensymphony.xwork2.ActionSupport;
|
||||
import com.opensymphony.xwork2.DefaultLocaleProvider;
|
||||
import com.opensymphony.xwork2.ValidationAwareSupport;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
@@ -101,6 +100,53 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
};
|
||||
|
||||
public static final UploadedFile NULL_CONTENT = new UploadedFile() {
|
||||
@Override
|
||||
public Long length() {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputName() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private FileUploadInterceptor interceptor;
|
||||
private File tempDir;
|
||||
|
||||
@@ -203,6 +249,22 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertTrue(msg.indexOf("inputName") > 0);
|
||||
}
|
||||
|
||||
public void testAcceptFileWithNoContent() {
|
||||
interceptor.setAllowedTypes("text/plain");
|
||||
|
||||
ValidationAwareSupport validation = new ValidationAwareSupport();
|
||||
boolean notOk = interceptor.acceptFile(validation, NULL_CONTENT, "filename.html", "text/plain", "inputName");
|
||||
|
||||
assertFalse(notOk);
|
||||
assertFalse(validation.getFieldErrors().isEmpty());
|
||||
assertTrue(validation.hasErrors());
|
||||
List<String> errors = validation.getFieldErrors().get("inputName");
|
||||
assertEquals(1, errors.size());
|
||||
String msg = errors.get(0);
|
||||
assertTrue(msg.startsWith("Error uploading:"));
|
||||
assertTrue(msg.indexOf("inputName") > 0);
|
||||
}
|
||||
|
||||
public void testAcceptFileWithMaxSize() throws Exception {
|
||||
interceptor.setMaximumSize(10L);
|
||||
|
||||
@@ -728,68 +790,6 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
|
||||
}
|
||||
|
||||
public void testUnacceptedFieldName() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.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");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
ActionFileUploadInterceptorTest.MyFileUploadAction action = container.inject(ActionFileUploadInterceptorTest.MyFileUploadAction.class);
|
||||
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
mai.setAction(action);
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext()
|
||||
.withServletRequest(createMultipartRequestMaxSize(req, 2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(action.hasActionErrors());
|
||||
assertNull(action.getUploadFiles());
|
||||
}
|
||||
|
||||
public void testUnacceptedFileName() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.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");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
ActionFileUploadInterceptorTest.MyFileUploadAction action = container.inject(ActionFileUploadInterceptorTest.MyFileUploadAction.class);
|
||||
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
mai.setAction(action);
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext()
|
||||
.withServletRequest(createMultipartRequestMaxSize(req, 2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(action.hasActionErrors());
|
||||
assertNull(action.getUploadFiles());
|
||||
}
|
||||
|
||||
private String encodeTextFile(String filename, String contentType, String content) {
|
||||
return "\r\n" +
|
||||
"--" +
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-async-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-cdi-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-config-browser-plugin</artifactId>
|
||||
|
||||
+5
-11
@@ -19,12 +19,12 @@
|
||||
package org.apache.struts2.config_browser;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.reflection.ReflectionContextFactory;
|
||||
import com.opensymphony.xwork2.util.reflection.ReflectionException;
|
||||
import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
|
||||
import com.opensymphony.xwork2.validator.Validator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.ActionContext;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
@@ -49,19 +49,13 @@ public class ShowValidatorAction extends ListValidatorsAction {
|
||||
private Set<PropertyInfo> properties = Collections.emptySet();
|
||||
private int selected = 0;
|
||||
|
||||
ReflectionProvider reflectionProvider;
|
||||
ReflectionContextFactory reflectionContextFactory;
|
||||
private ReflectionProvider reflectionProvider;
|
||||
|
||||
@Inject
|
||||
public void setReflectionProvider(ReflectionProvider prov) {
|
||||
this.reflectionProvider = prov;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setReflectionContextFactory(ReflectionContextFactory fac) {
|
||||
this.reflectionContextFactory = fac;
|
||||
}
|
||||
|
||||
public int getSelected() {
|
||||
return selected;
|
||||
}
|
||||
@@ -85,7 +79,6 @@ public class ShowValidatorAction extends ListValidatorsAction {
|
||||
Validator validator = getSelectedValidator();
|
||||
properties = new TreeSet<>();
|
||||
try {
|
||||
Map<String, Object> context = reflectionContextFactory.createDefaultContext(validator);
|
||||
BeanInfo beanInfoFrom;
|
||||
try {
|
||||
beanInfoFrom = Introspector.getBeanInfo(validator.getClass(), Object.class);
|
||||
@@ -97,6 +90,7 @@ public class ShowValidatorAction extends ListValidatorsAction {
|
||||
|
||||
PropertyDescriptor[] pds = beanInfoFrom.getPropertyDescriptors();
|
||||
|
||||
Map<String, Object> context = ActionContext.getContext().getContextMap();
|
||||
for (PropertyDescriptor pd : pds) {
|
||||
String name = pd.getName();
|
||||
Object value = null;
|
||||
@@ -113,9 +107,9 @@ public class ShowValidatorAction extends ListValidatorsAction {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Unable to retrieve properties.", e);
|
||||
LOG.warn("Unable to retrieve properties.", e);
|
||||
}
|
||||
addActionError("Unable to retrieve properties: " + e.toString());
|
||||
addActionError("Unable to retrieve properties: " + e);
|
||||
}
|
||||
|
||||
if (hasErrors()) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-convention-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-dwr-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-embeddedjsp-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-gxp-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jasperreports-plugin</artifactId>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-javatemplates-plugin</artifactId>
|
||||
|
||||
+3
@@ -23,6 +23,8 @@ package org.apache.struts2.views.java.simple;
|
||||
import org.apache.struts2.components.ActionError;
|
||||
import org.apache.struts2.components.Anchor;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -72,6 +74,7 @@ public class ActionErrorTest extends AbstractTest {
|
||||
//errors are needed to setup stack
|
||||
super.setUp();
|
||||
this.tag = new ActionError(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -23,6 +23,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.ActionError;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -72,6 +74,7 @@ public class ActionMessageTest extends AbstractTest {
|
||||
//errors are needed to setup stack
|
||||
super.setUp();
|
||||
this.tag = new ActionError(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -24,6 +24,8 @@ import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecke
|
||||
import org.apache.struts2.components.Anchor;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.components.ServletUrlRenderer;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class AnchorTest extends AbstractTest {
|
||||
private Anchor tag;
|
||||
@@ -111,6 +113,7 @@ public class AnchorTest extends AbstractTest {
|
||||
this.tag = new Anchor(stack, request, response);
|
||||
this.tag.setUrlRenderer(new ServletUrlRenderer());
|
||||
this.tag.setNotExcludedAcceptedPatterns(new DefaultNotExcludedAcceptedPatternsChecker());
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -23,6 +23,8 @@ package org.apache.struts2.views.java.simple;
|
||||
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
|
||||
import org.apache.struts2.components.Checkbox;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class CheckboxTest extends AbstractCommonAttributesTest {
|
||||
private Checkbox tag;
|
||||
@@ -88,6 +90,7 @@ public class CheckboxTest extends AbstractCommonAttributesTest {
|
||||
super.setUp();
|
||||
tag = new Checkbox(stack, request, response);
|
||||
tag.setNotExcludedAcceptedPatterns(new DefaultNotExcludedAcceptedPatternsChecker());
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.DateTextField;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class DateTextFieldTest extends AbstractCommonAttributesTest {
|
||||
|
||||
@@ -56,6 +58,7 @@ public class DateTextFieldTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new DateTextField(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.FieldError;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -100,6 +102,7 @@ public class FieldErrorTest extends AbstractTest {
|
||||
//errors are needed to setup stack
|
||||
super.setUp();
|
||||
this.tag = new FieldError(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.File;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class FileTest extends AbstractCommonAttributesTest {
|
||||
private File tag;
|
||||
@@ -50,6 +52,7 @@ public class FileTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new File(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,8 @@ package org.apache.struts2.views.java.simple;
|
||||
import org.apache.struts2.components.Form;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.components.UrlRenderer;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
import org.easymock.EasyMock;
|
||||
|
||||
public class FormTest extends AbstractCommonAttributesTest {
|
||||
@@ -80,5 +82,6 @@ public class FormTest extends AbstractCommonAttributesTest {
|
||||
UrlRenderer renderer = EasyMock.createNiceMock(UrlRenderer.class);
|
||||
EasyMock.replay(renderer);
|
||||
tag.setUrlRenderer(renderer);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Head;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class HeadTest extends AbstractTest {
|
||||
private Head tag;
|
||||
@@ -39,6 +41,7 @@ public class HeadTest extends AbstractTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Head(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Hidden;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class HiddenTest extends AbstractTest {
|
||||
private Hidden tag;
|
||||
@@ -49,6 +51,7 @@ public class HiddenTest extends AbstractTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Hidden(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Label;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class LabelTest extends AbstractCommonAttributesTest {
|
||||
private Label tag;
|
||||
@@ -48,6 +50,7 @@ public class LabelTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Label(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Link;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class LinkTest extends AbstractTest {
|
||||
|
||||
@@ -103,5 +105,6 @@ public class LinkTest extends AbstractTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Link(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-9
@@ -22,15 +22,14 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Password;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class PasswordTest extends AbstractCommonAttributesTest {
|
||||
|
||||
private Password tag;
|
||||
|
||||
public void testRenderPassword() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Password(stack, request, response);
|
||||
|
||||
tag.setName("name");
|
||||
tag.setValue("val1");
|
||||
tag.setSize("10");
|
||||
@@ -51,9 +50,6 @@ public class PasswordTest extends AbstractCommonAttributesTest {
|
||||
}
|
||||
|
||||
public void testRenderPasswordShowIt() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Password(stack, request, response);
|
||||
|
||||
tag.setName("name");
|
||||
tag.setValue("val1");
|
||||
tag.setSize("10");
|
||||
@@ -75,7 +71,9 @@ public class PasswordTest extends AbstractCommonAttributesTest {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
//dont call base setup
|
||||
super.setUp();
|
||||
this.tag = new Password(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,8 +83,6 @@ public class PasswordTest extends AbstractCommonAttributesTest {
|
||||
|
||||
@Override
|
||||
protected UIBean getUIBean() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Password(stack, request, response);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Reset;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class ResetTest extends AbstractCommonAttributesTest {
|
||||
private Reset tag;
|
||||
@@ -69,6 +71,7 @@ public class ResetTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Reset(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -21,6 +21,8 @@ package org.apache.struts2.views.java.simple;
|
||||
import com.opensymphony.xwork2.security.DefaultNotExcludedAcceptedPatternsChecker;
|
||||
import org.apache.struts2.components.Script;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
|
||||
public class ScriptTest extends AbstractTest {
|
||||
@@ -72,5 +74,6 @@ public class ScriptTest extends AbstractTest {
|
||||
|
||||
this.tag = new Script(stack, request, response);
|
||||
tag.setNotExcludedAcceptedPatterns(new DefaultNotExcludedAcceptedPatternsChecker());
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Select;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -114,6 +116,7 @@ public class SelectTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Select(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.Submit;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class SubmitTest extends AbstractCommonAttributesTest {
|
||||
private Submit tag;
|
||||
@@ -154,6 +156,7 @@ public class SubmitTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Submit(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.TextArea;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class TextAreaTest extends AbstractCommonAttributesTest {
|
||||
private TextArea tag;
|
||||
@@ -71,6 +73,7 @@ public class TextAreaTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new TextArea(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -22,6 +22,8 @@ package org.apache.struts2.views.java.simple;
|
||||
|
||||
import org.apache.struts2.components.TextField;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
public class TextFieldTest extends AbstractCommonAttributesTest {
|
||||
private TextField tag;
|
||||
@@ -52,6 +54,7 @@ public class TextFieldTest extends AbstractCommonAttributesTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new TextField(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
@@ -23,6 +23,8 @@ package org.apache.struts2.views.java.simple;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import org.apache.struts2.components.Token;
|
||||
import org.apache.struts2.components.UIBean;
|
||||
import org.apache.struts2.interceptor.csp.CspNonceSource;
|
||||
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -49,6 +51,7 @@ public class TokenTest extends AbstractTest {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.tag = new Token(stack, request, response);
|
||||
this.tag.setCspNonceReader(new StrutsCspNonceReader(CspNonceSource.SESSION.name()));
|
||||
|
||||
ActionContext.of()
|
||||
.withSession(new HashMap<>())
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jfreechart-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-json-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-junit-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-oval-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-pell-multipart-plugin</artifactId>
|
||||
|
||||
-7
@@ -18,11 +18,9 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import http.utils.multipartrequest.ServletMultipartRequest;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
@@ -40,11 +38,6 @@ public class PellMultiPartRequest extends AbstractMultiPartRequest {
|
||||
|
||||
private ServletMultipartRequest multi;
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, required = false)
|
||||
public PellMultiPartRequest(String dmiValue) {
|
||||
super(dmiValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new request wrapper to handle multi-part data using methods adapted from Jason Pell's
|
||||
* multipart classes (see class description).
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-plexus-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-junit-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-mocks-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-tiles-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-sitemesh-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-spring-plugin</artifactId>
|
||||
|
||||
+88
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-testng-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-tiles-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-velocity-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-xslt-plugin</artifactId>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.7.2</version>
|
||||
<version>6.8.0</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_6_7_2</tag>
|
||||
<tag>STRUTS_6_8_0</tag>
|
||||
</scm>
|
||||
|
||||
<issueManagement>
|
||||
@@ -104,16 +104,16 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.build.outputTimestamp>2025-02-04T06:34:33Z</project.build.outputTimestamp>
|
||||
<project.build.outputTimestamp>2025-09-15T05:41:00Z</project.build.outputTimestamp>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
||||
<!-- dependency versions in alphanumeric order -->
|
||||
<asm.version>9.7.1</asm.version>
|
||||
<jackson.version>2.18.0</jackson.version>
|
||||
<log4j2.version>2.24.1</log4j2.version>
|
||||
<jackson.version>2.20.0</jackson.version>
|
||||
<log4j2.version>2.25.1</log4j2.version>
|
||||
<ognl.version>3.3.5</ognl.version>
|
||||
<slf4j.version>2.0.16</slf4j.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
<spring.platformVersion>5.3.39</spring.platformVersion>
|
||||
<tiles.version>3.0.8</tiles.version>
|
||||
<tiles-request.version>1.0.7</tiles-request.version>
|
||||
@@ -183,6 +183,15 @@
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk9plus</id>
|
||||
<activation>
|
||||
<jdk>[9,)</jdk>
|
||||
</activation>
|
||||
<properties>
|
||||
<maven.compiler.release>8</maven.compiler.release>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
@@ -249,7 +258,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<version>3.9.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -372,7 +381,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-wrapper-plugin</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.3.3</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
@@ -851,12 +860,12 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>4.4</version>
|
||||
<version>4.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.5</version>
|
||||
<version>1.6.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
@@ -985,7 +994,7 @@
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.26.3</version>
|
||||
<version>3.27.4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -1037,7 +1046,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.27.1</version>
|
||||
<version>1.28.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
Reference in New Issue
Block a user