mirror of
https://github.com/apache/struts.git
synced 2026-08-11 01:27:14 +00:00
Compare commits
147 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aca2eb07ef | |||
| 964590c9a9 | |||
| e5e6145c36 | |||
| 0f6d5dbb2f | |||
| db725518ee | |||
| ee38cd5b14 | |||
| 7a79576bb3 | |||
| 5cf746f538 | |||
| 689d17d274 | |||
| c7ae614824 | |||
| 273c3a1a99 | |||
| c774e23ef9 | |||
| 93c11aaf62 | |||
| 66c1ac431f | |||
| 7281d7e2ee | |||
| 6950fcb75f | |||
| dc774c484f | |||
| 19698e1fd2 | |||
| cda2a2b231 | |||
| cdc931e26f | |||
| db0bd43855 | |||
| 86aee4b0ea | |||
| 29422e47fe | |||
| e08f637be9 | |||
| 4939d3cef8 | |||
| ca5e82309c | |||
| ac6c88ad4b | |||
| c320181275 | |||
| f459981022 | |||
| 6648cbdb23 | |||
| ac6095d1ae | |||
| d3727070b8 | |||
| a35cfebf5a | |||
| c5d760ce58 | |||
| 154132d469 | |||
| 43f1197961 | |||
| 0c4e21145d | |||
| 3aa63e8cbb | |||
| 82491eff87 | |||
| d9ed77d3f8 | |||
| 8283ded5e2 | |||
| 1ddf446f78 | |||
| 94b2ee7ce5 | |||
| 43d180f362 | |||
| 93fa72ee13 | |||
| 9d2a7649c5 | |||
| c553c93fab | |||
| 45b06dd69c | |||
| a84f85798b | |||
| 7edcac886b | |||
| bb3edaec06 | |||
| 1c59b45a23 | |||
| de9aacef61 | |||
| e3ca240b70 | |||
| 0a2861b8a3 | |||
| 694b97cc3c | |||
| 42e8ad1cc2 | |||
| e9eae281c3 | |||
| 07f0d1eddb | |||
| f913c407e9 | |||
| 6d6097c729 | |||
| d384c824bd | |||
| d1413b01c0 | |||
| f8fcdbde50 | |||
| ade8af3772 | |||
| caa34a4972 | |||
| e6d1f59de7 | |||
| 9afb24bdac | |||
| f0db577e99 | |||
| 4aa1cbc56b | |||
| 245eb90aa8 | |||
| ce24675221 | |||
| 889a4180cd | |||
| 93f0a5116b | |||
| b5fca66ad0 | |||
| ace6a713c4 | |||
| 9328ae7ec1 | |||
| 737542a031 | |||
| 6db4c5150e | |||
| 9a30a9f955 | |||
| 11d84a26c3 | |||
| 84d350dace | |||
| e9e3c0a252 | |||
| d024ccdd5f | |||
| e2215c852d | |||
| eb469779f7 | |||
| 372aad2c6c | |||
| e635b5973a | |||
| 51bf8de716 | |||
| b836072956 | |||
| d07792a40a | |||
| a358db5853 | |||
| 3294ed08d1 | |||
| abd824fdd2 | |||
| 218e634bf1 | |||
| fd7e74867c | |||
| 27f0b56966 | |||
| 8ef55db35f | |||
| fd2cf91601 | |||
| f8a8172a3e | |||
| 71d28ed90d | |||
| 77285186df | |||
| 028fedd306 | |||
| 8087ba97ee | |||
| e299bba112 | |||
| bd783a0e19 | |||
| c37a6edb62 | |||
| ac9b9ad89d | |||
| b68b6ab7c6 | |||
| 5057aeac83 | |||
| 7843cd5e1c | |||
| 2513fcb292 | |||
| 644bd1f8ca | |||
| e7a13b9637 | |||
| 8c161f431d | |||
| 2de30e72a9 | |||
| 65c0427eae | |||
| 9f4b67a9a2 | |||
| cf74a4450c | |||
| ec18f0eef1 | |||
| cde86457ab | |||
| 1e56b7ce68 | |||
| 775febbdfa | |||
| 77acd665ef | |||
| 54a7c7094f | |||
| 00533db153 | |||
| 71d77df3f3 | |||
| b50616942b | |||
| 728d695ce1 | |||
| ceb0a9a737 | |||
| 49b9c0c78c | |||
| 56d8361b41 | |||
| bf7737fa07 | |||
| f106b20983 | |||
| 6df80041e3 | |||
| 770d311105 | |||
| 0a71e2c3b9 | |||
| a57c2882e7 | |||
| b2c7542265 | |||
| 4c60f39c7a | |||
| 5d79301235 | |||
| e9154b922c | |||
| 4c5f2b0266 | |||
| bf3f407b5f | |||
| 4255da3ee9 | |||
| ad576f0fd5 | |||
| eca0666f0a |
@@ -13,7 +13,13 @@ notifications:
|
||||
github:
|
||||
del_branch_on_merge: true
|
||||
protected_branches:
|
||||
master: { }
|
||||
master:
|
||||
# contexts are the names of checks that must pass.
|
||||
contexts:
|
||||
- build
|
||||
required_pull_request_reviews:
|
||||
require_code_owner_reviews: true
|
||||
required_approving_review_count: 1
|
||||
autolink_jira:
|
||||
- WW
|
||||
dependabot_alerts: true
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
publish_results: true
|
||||
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # 4.1.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # 4.3.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
@@ -32,6 +32,7 @@ jobs:
|
||||
sonarcloud:
|
||||
name: Scan
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -5,14 +5,14 @@
|
||||
# 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.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
||||
|
||||
Vendored
+5
-5
@@ -67,7 +67,7 @@ pipeline {
|
||||
MAVEN_OPTS = "-Xmx1024m"
|
||||
}
|
||||
stages {
|
||||
stage('Test') {
|
||||
stage('Test & Coverage') {
|
||||
steps {
|
||||
sh './mvnw -B verify -Pcoverage -DskipAssembly --no-transfer-progress'
|
||||
}
|
||||
@@ -86,7 +86,7 @@ pipeline {
|
||||
}
|
||||
steps {
|
||||
withCredentials([string(credentialsId: 'asf-struts-sonarcloud', variable: 'SONARCLOUD_TOKEN')]) {
|
||||
sh './mvnw -B -Pcoverage -DskipAssembly -Dsonar.login=${SONARCLOUD_TOKEN} verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar'
|
||||
sh './mvnw -B -Pcoverage -DskipAssembly -Dsonar.login=${SONARCLOUD_TOKEN} verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar --no-transfer-progress'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ pipeline {
|
||||
dir("local-snapshots-dir/") {
|
||||
deleteDir()
|
||||
}
|
||||
sh './mvnw -B source:jar javadoc:jar -DskipTests -DskipAssembly'
|
||||
sh './mvnw -B source:jar javadoc:jar -DskipTests -DskipAssembly --no-transfer-progress'
|
||||
}
|
||||
}
|
||||
stage('Deploy Snapshot') {
|
||||
@@ -111,7 +111,7 @@ pipeline {
|
||||
}
|
||||
steps {
|
||||
withCredentials([file(credentialsId: 'lukaszlenart-repository-access-token', variable: 'CUSTOM_SETTINGS')]) {
|
||||
sh './mvnw -s \${CUSTOM_SETTINGS} deploy -DskipTests -DskipAssembly'
|
||||
sh './mvnw -s \${CUSTOM_SETTINGS} deploy -DskipTests -DskipAssembly --no-transfer-progress'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh './mvnw -B package -DskipTests'
|
||||
sh './mvnw -B package -DskipTests --no-transfer-progress'
|
||||
sshPublisher(publishers: [
|
||||
sshPublisherDesc(
|
||||
configName: 'Nightlies',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
The Apache Struts web framework
|
||||
-------------------------------
|
||||
|
||||
[](https://ci-builds.apache.org/job/Struts/job/Struts%20Core/job/master/)
|
||||
[](https://ci-builds.apache.org/job/Struts/job/Struts%20Core/job/master/)
|
||||
[](https://github.com/apache/struts/actions/workflows/maven.yml)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/org.apache.struts/struts2-core/)
|
||||
[](https://javadoc.io/doc/org.apache.struts/struts2-core)
|
||||
|
||||
+6
-6
@@ -2,13 +2,13 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please vist the [Releases](https://struts.apache.org/releases.html#prior-releases) page to see full information about each version
|
||||
Please visit the [Releases](https://struts.apache.org/releases.html#prior-releases) page to see full information about each version
|
||||
and what potential vulnerability it can have:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 6.0.0 | :white_check_mark: |
|
||||
| 2.5.30 | :white_check_mark: |
|
||||
|---------|--------------------|
|
||||
| 6.x | :white_check_mark: |
|
||||
| 2.5.x | :white_check_mark: |
|
||||
|
||||
## Reporting New Security Issues with thr Apache Struts
|
||||
|
||||
@@ -28,8 +28,8 @@ All mail sent to this address that does not relate to security problems in the A
|
||||
```
|
||||
|
||||
Note that all networked servers are subject to denial of service attacks, and we cannot promise magic
|
||||
workarounds to generic problems (such as a client streaming lots of data to your server, or re-requesting
|
||||
the same URL repeatedly). In general our philosophy is to avoid any attacks which can cause the server
|
||||
workarounds to generic problems (such as a client streaming lots of data to your server, or requesting
|
||||
the same URL repeatedly). In general, our philosophy is to avoid any attacks that can cause the server
|
||||
to consume resources in a non-linear relationship to the size of inputs.
|
||||
|
||||
The mailing address is: [security@struts.apache.org](mailto:security@struts.apache.org)
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</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>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
|
||||
@@ -24,9 +24,15 @@ import com.opensymphony.xwork2.ActionSupport;
|
||||
import com.opensymphony.xwork2.Validateable;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -89,6 +95,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return leftSideCartoonCharacters;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setLeftSideCartoonCharacters(List leftSideCartoonCharacters) {
|
||||
this.leftSideCartoonCharacters = leftSideCartoonCharacters;
|
||||
}
|
||||
@@ -98,6 +105,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return rightSideCartoonCharacters;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setRightSideCartoonCharacters(List rightSideCartoonCharacters) {
|
||||
this.rightSideCartoonCharacters = rightSideCartoonCharacters;
|
||||
}
|
||||
@@ -107,6 +115,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return favouriteVehicalType;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFavouriteVehicalType(String favouriteVehicalType) {
|
||||
this.favouriteVehicalType = favouriteVehicalType;
|
||||
}
|
||||
@@ -115,6 +124,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return favouriteVehicalSpecific;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFavouriteVehicalSpecific(String favouriteVehicalSpecific) {
|
||||
this.favouriteVehicalSpecific = favouriteVehicalSpecific;
|
||||
}
|
||||
@@ -145,6 +155,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return name;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -153,6 +164,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setBirthday(Date birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
@@ -161,6 +173,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return bio;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setBio(String bio) {
|
||||
this.bio = bio;
|
||||
}
|
||||
@@ -169,6 +182,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return favouriteColor;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFavouriteColor(String favoriteColor) {
|
||||
this.favouriteColor = favoriteColor;
|
||||
}
|
||||
@@ -177,6 +191,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return friends;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFriends(List friends) {
|
||||
this.friends = friends;
|
||||
}
|
||||
@@ -193,6 +208,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return legalAge;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setLegalAge(boolean legalAge) {
|
||||
this.legalAge = legalAge;
|
||||
}
|
||||
@@ -201,6 +217,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return state;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
@@ -209,6 +226,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return region;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setRegion(String region) {
|
||||
this.region = region;
|
||||
}
|
||||
@@ -229,6 +247,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
this.pictureFileName = pictureFileName;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFavouriteLanguage(String favouriteLanguage) {
|
||||
this.favouriteLanguage = favouriteLanguage;
|
||||
}
|
||||
@@ -237,7 +256,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return favouriteLanguage;
|
||||
}
|
||||
|
||||
|
||||
@StrutsParameter
|
||||
public void setThoughts(String thoughts) {
|
||||
this.thoughts = thoughts;
|
||||
}
|
||||
@@ -250,6 +269,7 @@ public class UITagExample extends ActionSupport implements Validateable {
|
||||
return wakeup;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setWakeup(Date wakeup) {
|
||||
this.wakeup = wakeup;
|
||||
}
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.showcase.action;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
import org.apache.struts2.showcase.model.MyDto;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
/**
|
||||
* This class supports {@link com.atlassian.confluence.stateless.webdriver.selenium3.security.StrutsParametersTest}
|
||||
* which prevents critical security regressions. Do NOT modify without understanding the motivation behind the tests and
|
||||
* the implications of any changes.
|
||||
*/
|
||||
public class ParamsAnnotationAction extends ActionSupport {
|
||||
|
||||
@StrutsParameter
|
||||
public String varToPrint;
|
||||
|
||||
public String publicField = "no";
|
||||
|
||||
@StrutsParameter
|
||||
public String publicFieldAnnotated = "no";
|
||||
|
||||
private String privateField = "no";
|
||||
|
||||
public int[] publicArray = new int[]{0};
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public int[] publicArrayAnnotated = new int[]{0};
|
||||
|
||||
public List<String> publicList = new ArrayList<>(singletonList("no"));
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public List<String> publicListAnnotated = new ArrayList<>(singletonList("no"));
|
||||
|
||||
private List<String> privateList = new ArrayList<>(singletonList("no"));
|
||||
|
||||
public Map<String, String> publicMap = new HashMap<>(singletonMap("key", "no"));
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public Map<String, String> publicMapAnnotated = new HashMap<>(singletonMap("key", "no"));
|
||||
|
||||
public MyDto publicMyDto = new MyDto();
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public MyDto publicMyDtoAnnotated = new MyDto();
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public MyDto publicMyDtoAnnotatedDepthOne = new MyDto();
|
||||
|
||||
private MyDto privateMyDto = new MyDto();
|
||||
|
||||
public void setPrivateFieldMethod(String privateField) {
|
||||
this.privateField = privateField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setPrivateFieldMethodAnnotated(String privateField) {
|
||||
this.privateField = privateField;
|
||||
}
|
||||
|
||||
public List<String> getPrivateListMethod() {
|
||||
return privateList;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public List<String> getPrivateListMethodAnnotated() {
|
||||
return privateList;
|
||||
}
|
||||
|
||||
public MyDto getUnsafeMethodMyDto() {
|
||||
return privateMyDto;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public MyDto getSafeMethodMyDto() {
|
||||
return privateMyDto;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public MyDto getSafeMethodMyDtoDepthOne() {
|
||||
return privateMyDto;
|
||||
}
|
||||
|
||||
public String renderVarToPrint() throws ReflectiveOperationException {
|
||||
if (varToPrint == null) {
|
||||
return "null";
|
||||
}
|
||||
Field field = this.getClass().getDeclaredField(varToPrint);
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
return String.format("%s{%s}", varToPrint,
|
||||
field.getType().isArray() ? stringifyArray(field.get(this)) : field.get(this));
|
||||
} finally {
|
||||
field.setAccessible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private String stringifyArray(Object array) {
|
||||
switch (array.getClass().getComponentType().getName()) {
|
||||
case "int":
|
||||
return Arrays.toString((int[]) array);
|
||||
default:
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ package org.apache.struts2.showcase.action;
|
||||
import com.opensymphony.xwork2.Preparable;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
import org.apache.struts2.showcase.dao.Dao;
|
||||
import org.apache.struts2.showcase.dao.SkillDao;
|
||||
import org.apache.struts2.showcase.model.Skill;
|
||||
@@ -71,6 +72,7 @@ public class SkillAction extends AbstractCRUDAction implements Preparable {
|
||||
return skillDao;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public Skill getCurrentSkill() {
|
||||
return currentSkill;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.apache.struts2.showcase.async;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -34,10 +35,12 @@ public class ChatRoomAction extends ActionSupport {
|
||||
|
||||
private static final List<String> messages = new ArrayList<>();
|
||||
|
||||
@StrutsParameter
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setLastIndex(Integer lastIndex) {
|
||||
this.lastIndex = lastIndex;
|
||||
}
|
||||
|
||||
+3
-1
@@ -21,6 +21,7 @@
|
||||
package org.apache.struts2.showcase.conversion;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
@@ -30,7 +31,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class AddressAction extends ActionSupport {
|
||||
|
||||
private Set<Address> addresses = new LinkedHashSet<Address>();
|
||||
private Set<Address> addresses = new LinkedHashSet<>();
|
||||
|
||||
public String input() throws Exception {
|
||||
return SUCCESS;
|
||||
@@ -41,6 +42,7 @@ public class AddressAction extends ActionSupport {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public Set<Address> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
+2
@@ -21,6 +21,7 @@
|
||||
package org.apache.struts2.showcase.conversion;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
@@ -47,6 +48,7 @@ public class OperationsEnumAction extends ActionSupport {
|
||||
return this.selectedOperations;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setSelectedOperations(List<OperationsEnum> selectedOperations) {
|
||||
this.selectedOperations = selectedOperations;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package org.apache.struts2.showcase.conversion;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,6 +37,7 @@ public class PersonAction extends ActionSupport {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public List<Person> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ package org.apache.struts2.showcase.filedownload;
|
||||
|
||||
import com.opensymphony.xwork2.Action;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -38,6 +39,7 @@ public class FileDownloadAction implements Action {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setInputPath(String value) {
|
||||
inputPath = sanitizeInputPath(value);
|
||||
}
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ package org.apache.struts2.showcase.fileupload;
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,6 +66,7 @@ public class FileUploadAction extends ActionSupport implements UploadedFilesAwar
|
||||
return caption;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setCaption(String caption) {
|
||||
this.caption = caption;
|
||||
}
|
||||
|
||||
+36
-46
@@ -22,61 +22,51 @@
|
||||
package org.apache.struts2.showcase.fileupload;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Showcase action - mutiple file upload using array.
|
||||
*
|
||||
* @version $Date$ $Id$
|
||||
*/
|
||||
public class MultipleFileUploadUsingArrayAction extends ActionSupport {
|
||||
public class MultipleFileUploadUsingArrayAction extends ActionSupport implements UploadedFilesAware {
|
||||
|
||||
private File[] uploads = new File[0];
|
||||
private String[] uploadFileNames = new String[0];
|
||||
private String[] uploadContentTypes = new String[0];
|
||||
private List<UploadedFile> uploadedFiles;
|
||||
|
||||
public String upload() throws Exception {
|
||||
System.out.println("\n\n upload2");
|
||||
System.out.println("files:");
|
||||
for (UploadedFile u : uploadedFiles) {
|
||||
System.out.println("*** " + u + "\t" + u.length());
|
||||
}
|
||||
System.out.println("filenames:");
|
||||
for (String n : getUploadFileNames()) {
|
||||
System.out.println("*** " + n);
|
||||
}
|
||||
System.out.println("content types:");
|
||||
for (String c : getUploadContentTypes()) {
|
||||
System.out.println("*** " + c);
|
||||
}
|
||||
System.out.println("\n\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
public String upload() throws Exception {
|
||||
System.out.println("\n\n upload2");
|
||||
System.out.println("files:");
|
||||
for (File u : uploads) {
|
||||
System.out.println("*** " + u + "\t" + u.length());
|
||||
}
|
||||
System.out.println("filenames:");
|
||||
for (String n : uploadFileNames) {
|
||||
System.out.println("*** " + n);
|
||||
}
|
||||
System.out.println("content types:");
|
||||
for (String c : uploadContentTypes) {
|
||||
System.out.println("*** " + c);
|
||||
}
|
||||
System.out.println("\n\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
@Override
|
||||
public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
|
||||
this.uploadedFiles = uploadedFiles;
|
||||
}
|
||||
|
||||
public File[] getUpload() {
|
||||
return this.uploads;
|
||||
}
|
||||
private String[] getUploadFileNames() {
|
||||
return this.uploadedFiles.stream()
|
||||
.map(UploadedFile::getOriginalName)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public void setUpload(File[] upload) {
|
||||
this.uploads = upload;
|
||||
}
|
||||
private String[] getUploadContentTypes() {
|
||||
return this.uploadedFiles.stream()
|
||||
.map(UploadedFile::getContentType)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public String[] getUploadFileName() {
|
||||
return this.uploadFileNames;
|
||||
}
|
||||
|
||||
public void setUploadFileName(String[] uploadFileName) {
|
||||
this.uploadFileNames = uploadFileName;
|
||||
}
|
||||
|
||||
public String[] getUploadContentType() {
|
||||
return this.uploadContentTypes;
|
||||
}
|
||||
|
||||
public void setUploadContentType(String[] uploadContentType) {
|
||||
this.uploadContentTypes = uploadContentType;
|
||||
}
|
||||
}
|
||||
// END SNIPPET: entire-file
|
||||
+40
-48
@@ -22,64 +22,56 @@
|
||||
package org.apache.struts2.showcase.fileupload;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Showcase action - multiple file upload using List
|
||||
*
|
||||
* @version $Date$ $Id$
|
||||
*/
|
||||
public class MultipleFileUploadUsingListAction extends ActionSupport {
|
||||
public class MultipleFileUploadUsingListAction extends ActionSupport implements UploadedFilesAware {
|
||||
|
||||
private List<File> uploads = new ArrayList<>();
|
||||
private List<String> uploadFileNames = new ArrayList<>();
|
||||
private List<String> uploadContentTypes = new ArrayList<>();
|
||||
private List<UploadedFile> uploads = new ArrayList<>();
|
||||
|
||||
public List<UploadedFile> getUpload() {
|
||||
return this.uploads;
|
||||
}
|
||||
|
||||
public List<File> getUpload() {
|
||||
return this.uploads;
|
||||
}
|
||||
@Override
|
||||
public void withUploadedFiles(List<UploadedFile> uploads) {
|
||||
this.uploads = uploads;
|
||||
}
|
||||
|
||||
public void setUpload(List<File> uploads) {
|
||||
this.uploads = uploads;
|
||||
}
|
||||
private List<String> getUploadFileNames() {
|
||||
return this.uploads.stream()
|
||||
.map(UploadedFile::getOriginalName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getUploadFileName() {
|
||||
return this.uploadFileNames;
|
||||
}
|
||||
private List<String> getUploadContentTypes() {
|
||||
return this.uploads.stream()
|
||||
.map(UploadedFile::getContentType)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void setUploadFileName(List<String> uploadFileNames) {
|
||||
this.uploadFileNames = uploadFileNames;
|
||||
}
|
||||
|
||||
public List<String> getUploadContentType() {
|
||||
return this.uploadContentTypes;
|
||||
}
|
||||
|
||||
public void setUploadContentType(List<String> contentTypes) {
|
||||
this.uploadContentTypes = contentTypes;
|
||||
}
|
||||
|
||||
public String upload() throws Exception {
|
||||
|
||||
System.out.println("\n\n upload1");
|
||||
System.out.println("files:");
|
||||
for (File u : uploads) {
|
||||
System.out.println("*** " + u + "\t" + u.length());
|
||||
}
|
||||
System.out.println("filenames:");
|
||||
for (String n : uploadFileNames) {
|
||||
System.out.println("*** " + n);
|
||||
}
|
||||
System.out.println("content types:");
|
||||
for (String c : uploadContentTypes) {
|
||||
System.out.println("*** " + c);
|
||||
}
|
||||
System.out.println("\n\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
// END SNIPPET: entire-file
|
||||
public String upload() throws Exception {
|
||||
System.out.println("\n\n upload1");
|
||||
System.out.println("files:");
|
||||
for (UploadedFile u : uploads) {
|
||||
System.out.println("*** " + u + "\t" + u.length());
|
||||
}
|
||||
System.out.println("filenames:");
|
||||
for (String n : getUploadFileNames()) {
|
||||
System.out.println("*** " + n);
|
||||
}
|
||||
System.out.println("content types:");
|
||||
for (String c : getUploadContentTypes()) {
|
||||
System.out.println("*** " + c);
|
||||
}
|
||||
System.out.println("\n\n");
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.showcase.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
public class MyDto {
|
||||
|
||||
public String str = "no";
|
||||
|
||||
public Map<String, String> map = new HashMap<>(singletonMap("key", "no"));
|
||||
public int[] array = new int[]{0};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "str=" + str + ", map=" + map + ", array=" + Arrays.toString(array);
|
||||
}
|
||||
}
|
||||
+11
@@ -20,6 +20,8 @@
|
||||
*/
|
||||
package org.apache.struts2.showcase.validation;
|
||||
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
/**
|
||||
@@ -44,6 +46,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return dateValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setDateValidatorField(Date dateValidatorField) {
|
||||
this.dateValidatorField = dateValidatorField;
|
||||
}
|
||||
@@ -52,6 +55,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return emailValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setEmailValidatorField(String emailValidatorField) {
|
||||
this.emailValidatorField = emailValidatorField;
|
||||
}
|
||||
@@ -60,6 +64,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return integerValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setIntegerValidatorField(Integer integerValidatorField) {
|
||||
this.integerValidatorField = integerValidatorField;
|
||||
}
|
||||
@@ -68,6 +73,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return regexValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setRegexValidatorField(String regexValidatorField) {
|
||||
this.regexValidatorField = regexValidatorField;
|
||||
}
|
||||
@@ -76,6 +82,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return requiredStringValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setRequiredStringValidatorField(String requiredStringValidatorField) {
|
||||
this.requiredStringValidatorField = requiredStringValidatorField;
|
||||
}
|
||||
@@ -84,6 +91,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return requiredValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setRequiredValidatorField(String requiredValidatorField) {
|
||||
this.requiredValidatorField = requiredValidatorField;
|
||||
}
|
||||
@@ -92,6 +100,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return stringLengthValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setStringLengthValidatorField(String stringLengthValidatorField) {
|
||||
this.stringLengthValidatorField = stringLengthValidatorField;
|
||||
}
|
||||
@@ -100,6 +109,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return fieldExpressionValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setFieldExpressionValidatorField(
|
||||
String fieldExpressionValidatorField) {
|
||||
this.fieldExpressionValidatorField = fieldExpressionValidatorField;
|
||||
@@ -109,6 +119,7 @@ public class FieldValidatorsExampleAction extends AbstractValidationActionSuppor
|
||||
return urlValidatorField;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setUrlValidatorField(String urlValidatorField) {
|
||||
this.urlValidatorField = urlValidatorField;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package org.apache.struts2.showcase.wait;
|
||||
|
||||
import com.opensymphony.xwork2.ActionSupport;
|
||||
import org.apache.struts2.interceptor.parameter.StrutsParameter;
|
||||
|
||||
/**
|
||||
* Example to illustrate the <code>execAndWait</code> interceptor.
|
||||
@@ -41,6 +42,7 @@ public class LongProcessAction extends ActionSupport {
|
||||
return time;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setTime(int time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
"https://struts.apache.org/dtds/struts-6.0.dtd">
|
||||
|
||||
<struts>
|
||||
|
||||
<constant name="struts.multipart.maxSize" value="10240" />
|
||||
|
||||
<package name="fileupload" extends="struts-default" namespace="/fileupload">
|
||||
|
||||
<action name="upload" class="org.apache.struts2.showcase.fileupload.FileUploadAction" method="input">
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
<!DOCTYPE struts PUBLIC
|
||||
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
|
||||
"https://struts.apache.org/dtds/struts-6.0.dtd">
|
||||
|
||||
<struts>
|
||||
<package name="params-annotation" extends="velocity-default" namespace="/paramsannotation">
|
||||
<action name="test" class="org.apache.struts2.showcase.action.ParamsAnnotationAction">
|
||||
<result type="velocity">/WEB-INF/paramsannotation.vm</result>
|
||||
</action>
|
||||
</package>
|
||||
</struts>
|
||||
@@ -35,17 +35,7 @@
|
||||
<constant name="struts.action.extension" value="action,," />
|
||||
|
||||
<constant name="struts.allowlist.enable" value="true" />
|
||||
<constant name="struts.allowlist.packageNames"
|
||||
value="
|
||||
org.apache.struts2.showcase.model,
|
||||
org.apache.struts2.showcase.conversion
|
||||
"/>
|
||||
<constant name="struts.allowlist.classes"
|
||||
value="
|
||||
org.apache.struts2.showcase.UITagExample$Language,
|
||||
org.apache.struts2.showcase.UITagExample$VehicalType,
|
||||
org.apache.struts2.showcase.UITagExample$VehicalSpecific
|
||||
"/>
|
||||
<constant name="struts.parameters.requireAnnotations" value="true" />
|
||||
|
||||
<constant name="struts.convention.package.locators.basePackage" value="org.apache.struts2.showcase" />
|
||||
<constant name="struts.convention.result.path" value="/WEB-INF" />
|
||||
@@ -93,6 +83,8 @@
|
||||
|
||||
<include file="struts-dispatcher.xml" />
|
||||
|
||||
<include file="struts-params-annotation.xml" />
|
||||
|
||||
<package name="default" extends="struts-default">
|
||||
<interceptors>
|
||||
<interceptor-stack name="crudStack">
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
<li>FileName: <s:property value="fileName" /></li>
|
||||
<li>Original FileName: <s:property value="originalName" /></li>
|
||||
<li>File: <s:property value="uploadedFile" /></li>
|
||||
<li>Caption:<s:property value="caption" /></li>
|
||||
<li>Size: <s:property value="uploadSize" /></li>
|
||||
<li>Caption: <s:property value="caption" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#*
|
||||
* 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.
|
||||
*#
|
||||
<div id="output">$action.renderVarToPrint()</div>
|
||||
@@ -18,9 +18,6 @@
|
||||
*/
|
||||
package it.org.apache.struts2.showcase;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.html.HtmlFileInput;
|
||||
import org.htmlunit.html.HtmlForm;
|
||||
@@ -29,6 +26,10 @@ import org.htmlunit.html.HtmlPage;
|
||||
import org.htmlunit.html.HtmlSubmitInput;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class FileUploadTest {
|
||||
@@ -46,6 +47,42 @@ public class FileUploadTest {
|
||||
|
||||
try (FileWriter writer = new FileWriter(tempFile)) {
|
||||
writer.append("Some strings");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
uploadInput.setValue(tempFile.getAbsolutePath());
|
||||
final HtmlSubmitInput button = form.getInputByValue("Submit");
|
||||
final HtmlPage resultPage = button.click();
|
||||
|
||||
String content = resultPage.getVisibleText();
|
||||
assertThat(content).contains(
|
||||
"ContentType: text/plain",
|
||||
"Original FileName: " + tempFile.getName(),
|
||||
"Caption: some caption",
|
||||
"Size: 12"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadOverMaxSize() throws Exception {
|
||||
try (final WebClient webClient = new WebClient()) {
|
||||
final HtmlPage page = webClient.getPage(ParameterUtils.getBaseUrl() + "/fileupload/doUpload.action");
|
||||
final HtmlForm form = page.getFormByName("doUpload");
|
||||
HtmlInput captionInput = form.getInputByName("caption");
|
||||
HtmlFileInput uploadInput = form.getInputByName("upload");
|
||||
|
||||
captionInput.type("Large file");
|
||||
|
||||
File tempFile = File.createTempFile("testEmptyFile", ".txt");
|
||||
SecureRandom rng = new SecureRandom();
|
||||
tempFile.deleteOnExit();
|
||||
try (FileWriter writer = new FileWriter(tempFile)) {
|
||||
for (int i = 0; i < 10240; ++i) {
|
||||
String line = String.format("%s %s%n", rng.nextInt(), rng.nextInt());
|
||||
writer.append(line);
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
uploadInput.setValue(tempFile.getAbsolutePath());
|
||||
@@ -55,9 +92,7 @@ public class FileUploadTest {
|
||||
String content = resultPage.getVisibleText();
|
||||
System.out.println(content);
|
||||
assertThat(content).contains(
|
||||
"ContentType: text/plain",
|
||||
"Original FileName: " + tempFile.getName(),
|
||||
"Caption:some caption"
|
||||
"Request exceeded allowed size limit! Max size allowed is: 10,240!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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 it.org.apache.struts2.showcase;
|
||||
|
||||
import org.htmlunit.WebClient;
|
||||
import org.htmlunit.html.HtmlPage;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class StrutsParametersTest {
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
webClient = new WebClient();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
webClient.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_StringField_WithoutGetterSetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicField", "yes");
|
||||
params.put("varToPrint", "publicField");
|
||||
assertText(params, "publicField{no}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_StringField_WithoutGetterSetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicFieldAnnotated", "yes");
|
||||
params.put("varToPrint", "publicFieldAnnotated");
|
||||
assertText(params, "publicFieldAnnotated{yes}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_StringField_WithSetter_MethodNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("privateFieldMethod", "yes");
|
||||
params.put("varToPrint", "privateField");
|
||||
assertText(params, "privateField{no}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_StringField_WithSetter_MethodAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("privateFieldMethodAnnotated", "yes");
|
||||
params.put("varToPrint", "privateField");
|
||||
assertText(params, "privateField{yes}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_ArrayField_WithoutGetterSetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicArray[0]", "1");
|
||||
params.put("varToPrint", "publicArray");
|
||||
assertText(params, "publicArray{[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_ArrayField_WithoutGetterSetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicArrayAnnotated[0]", "1");
|
||||
params.put("varToPrint", "publicArrayAnnotated");
|
||||
assertText(params, "publicArrayAnnotated{[1]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_ListField_WithoutGetterSetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicList[0]", "yes");
|
||||
params.put("varToPrint", "publicList");
|
||||
assertText(params, "publicList{[no]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_ListField_WithoutGetterSetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicListAnnotated[0]", "yes");
|
||||
params.put("varToPrint", "publicListAnnotated");
|
||||
assertText(params, "publicListAnnotated{[yes]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_ListField_WithGetterNoSetter_MethodNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("privateListMethod[0]", "yes");
|
||||
params.put("varToPrint", "privateList");
|
||||
assertText(params, "privateList{[no]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_ListField_WithGetterNoSetter_MethodAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("privateListMethodAnnotated[0]", "yes");
|
||||
params.put("varToPrint", "privateList");
|
||||
assertText(params, "privateList{[yes]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MapField_WithoutGetterSetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMap['key']", "yes");
|
||||
params.put("varToPrint", "publicMap");
|
||||
assertText(params, "publicMap{{key=no}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MapField_WithoutGetterSetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMapAnnotated['key']", "yes");
|
||||
params.put("varToPrint", "publicMapAnnotated");
|
||||
assertText(params, "publicMapAnnotated{{key=yes}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MapField_Insert_WithoutGetterSetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMap[999]", "yes");
|
||||
params.put("varToPrint", "publicMap");
|
||||
assertText(params, "publicMap{{key=no}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MapField_Insert_WithoutGetterSetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMapAnnotated[999]", "yes");
|
||||
params.put("varToPrint", "publicMapAnnotated");
|
||||
assertText(params, "publicMapAnnotated{{999=yes, key=no}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MyDtoField_WithoutGetter_FieldNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMyDto.str", "yes");
|
||||
params.put("publicMyDto.map['key']", "yes");
|
||||
params.put("publicMyDto.array[0]", "1");
|
||||
params.put("varToPrint", "publicMyDto");
|
||||
assertText(params, "publicMyDto{str=no, map={key=no}, array=[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MyDtoField_WithoutGetter_FieldAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMyDtoAnnotated.str", "yes");
|
||||
params.put("publicMyDtoAnnotated.map['key']", "yes");
|
||||
params.put("publicMyDtoAnnotated.array[0]", "1");
|
||||
params.put("varToPrint", "publicMyDtoAnnotated");
|
||||
assertText(params, "publicMyDtoAnnotated{str=yes, map={key=yes}, array=[1]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void public_MyDtoField_WithoutGetter_FieldAnnotatedDepthOne() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("publicMyDtoAnnotatedDepthOne.str", "yes");
|
||||
params.put("publicMyDtoAnnotatedDepthOne.map['key']", "yes");
|
||||
params.put("publicMyDtoAnnotatedDepthOne.array[0]", "1");
|
||||
params.put("varToPrint", "publicMyDtoAnnotatedDepthOne");
|
||||
assertText(params, "publicMyDtoAnnotatedDepthOne{str=yes, map={key=no}, array=[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_MyDtoField_WithGetter_MethodNotAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("unsafeMethodMyDto.str", "yes");
|
||||
params.put("unsafeMethodMyDto.map['key']", "yes");
|
||||
params.put("unsafeMethodMyDto.array[0]", "1");
|
||||
params.put("varToPrint", "privateMyDto");
|
||||
assertText(params, "privateMyDto{str=no, map={key=no}, array=[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_MyDtoField_WithGetter_MethodNotAnnotated_Alternate() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("unsafeMethodMyDto['str']", "yes");
|
||||
params.put("unsafeMethodMyDto['map']['key']", "yes");
|
||||
params.put("unsafeMethodMyDto['map'][999]", "yes");
|
||||
params.put("unsafeMethodMyDto['array'][0]", "1");
|
||||
params.put("varToPrint", "privateMyDto");
|
||||
assertText(params, "privateMyDto{str=no, map={key=no}, array=[0]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_MyDtoField_WithGetter_MethodAnnotated() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("safeMethodMyDto.str", "yes");
|
||||
params.put("safeMethodMyDto.map['key']", "yes");
|
||||
params.put("safeMethodMyDto.array[0]", "1");
|
||||
params.put("varToPrint", "privateMyDto");
|
||||
assertText(params, "privateMyDto{str=yes, map={key=yes}, array=[1]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void private_MyDtoField_WithGetter_MethodAnnotatedDepthOne() throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("safeMethodMyDtoDepthOne.str", "yes");
|
||||
params.put("safeMethodMyDtoDepthOne.map['key']", "yes");
|
||||
params.put("safeMethodMyDtoDepthOne.array[0]", "1");
|
||||
params.put("varToPrint", "privateMyDto");
|
||||
assertText(params, "privateMyDto{str=yes, map={key=no}, array=[0]}");
|
||||
}
|
||||
|
||||
private void assertText(Map<String, String> params, String text) throws IOException {
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(ParameterUtils.getBaseUrl()).path("/paramsannotation/test.action");
|
||||
params.forEach(builder::queryParam);
|
||||
String url = builder.toUriString();
|
||||
HtmlPage page = webClient.getPage(url);
|
||||
String output = page.getElementById("output").asNormalizedText();
|
||||
assertEquals(text, output);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-assembly</artifactId>
|
||||
@@ -115,6 +115,7 @@
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<skipAssembly>false</skipAssembly>
|
||||
<descriptors>
|
||||
<descriptor>src/main/assembly/all.xml</descriptor>
|
||||
<descriptor>src/main/assembly/lib.xml</descriptor>
|
||||
|
||||
+4
-4
@@ -25,11 +25,11 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-bom</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Struts 2 Bill of Materials</name>
|
||||
@@ -44,7 +44,7 @@
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<struts-version.version>7.0.0-M1</struts-version.version>
|
||||
<struts-version.version>7.0.0-M4</struts-version.version>
|
||||
<maven.site.skip>true</maven.site.skip>
|
||||
<maven.site.deploy.skip>true</maven.site.deploy.skip>
|
||||
</properties>
|
||||
@@ -165,7 +165,7 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<tag>STRUTS_7_0_0_M1</tag>
|
||||
<tag>STRUTS_7_0_0_M4</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>
|
||||
|
||||
+2
-3
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -216,8 +216,7 @@
|
||||
<!-- File upload -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-fileupload2-jakarta</artifactId>
|
||||
<version>2.0.0-M1</version>
|
||||
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
|
||||
@@ -112,6 +112,7 @@ import org.apache.struts2.conversion.StrutsTypeConverterHolder;
|
||||
import org.apache.struts2.ognl.OgnlGuard;
|
||||
import org.apache.struts2.ognl.ProviderAllowlist;
|
||||
import org.apache.struts2.ognl.StrutsOgnlGuard;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -395,6 +396,7 @@ public class DefaultConfiguration implements Configuration {
|
||||
.factory(SecurityMemberAccess.class, Scope.PROTOTYPE)
|
||||
.factory(OgnlGuard.class, StrutsOgnlGuard.class, Scope.SINGLETON)
|
||||
.factory(ProviderAllowlist.class, Scope.SINGLETON)
|
||||
.factory(ThreadAllowlist.class, Scope.SINGLETON)
|
||||
|
||||
.factory(ValueSubstitutor.class, EnvsValueSubstitutor.class, Scope.SINGLETON);
|
||||
}
|
||||
|
||||
+3
-1
@@ -142,7 +142,9 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
providerAllowlist.clearAllowlist(this);
|
||||
if (providerAllowlist != null) {
|
||||
providerAllowlist.clearAllowlist(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected Class<?> allowAndLoadClass(String className) throws ClassNotFoundException {
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
import org.apache.struts2.ognl.ProviderAllowlist;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -75,6 +76,7 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
)));
|
||||
|
||||
private final ProviderAllowlist providerAllowlist;
|
||||
private final ThreadAllowlist threadAllowlist;
|
||||
private boolean allowStaticFieldAccess = true;
|
||||
private Set<Pattern> excludeProperties = emptySet();
|
||||
private Set<Pattern> acceptProperties = emptySet();
|
||||
@@ -89,8 +91,9 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
private boolean disallowDefaultPackageAccess = false;
|
||||
|
||||
@Inject
|
||||
public SecurityMemberAccess(@Inject ProviderAllowlist providerAllowlist) {
|
||||
public SecurityMemberAccess(@Inject ProviderAllowlist providerAllowlist, @Inject ThreadAllowlist threadAllowlist) {
|
||||
this.providerAllowlist = providerAllowlist;
|
||||
this.threadAllowlist = threadAllowlist;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,11 +102,11 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
* - block or allow access to properties (configurable-after-construction)
|
||||
*
|
||||
* @param allowStaticFieldAccess if set to true static fields (constants) will be accessible
|
||||
* @deprecated since 6.4.0, use {@link #SecurityMemberAccess(ProviderAllowlist)} instead.
|
||||
* @deprecated since 6.4.0, use {@link #SecurityMemberAccess(ProviderAllowlist, ThreadAllowlist)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public SecurityMemberAccess(boolean allowStaticFieldAccess) {
|
||||
this(null);
|
||||
this(null, null);
|
||||
useAllowStaticFieldAccess(String.valueOf(allowStaticFieldAccess));
|
||||
}
|
||||
|
||||
@@ -223,6 +226,7 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
return allowlistClasses.contains(clazz)
|
||||
|| ALLOWLIST_REQUIRED_CLASSES.contains(clazz)
|
||||
|| (providerAllowlist != null && providerAllowlist.getProviderAllowlist().contains(clazz))
|
||||
|| (threadAllowlist != null && threadAllowlist.getAllowlist().contains(clazz))
|
||||
|| isClassBelongsToPackages(clazz, ALLOWLIST_REQUIRED_PACKAGES)
|
||||
|| isClassBelongsToPackages(clazz, allowlistPackageNames);
|
||||
}
|
||||
|
||||
+13
-5
@@ -25,12 +25,14 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(DefaultAcceptedPatternsChecker.class);
|
||||
@@ -39,6 +41,12 @@ public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
"\\w+((\\.\\w+)|(\\[\\d+])|(\\(\\d+\\))|(\\['(\\w-?|[\\u4e00-\\u9fa5]-?)+'])|(\\('(\\w-?|[\\u4e00-\\u9fa5]-?)+'\\)))*"
|
||||
};
|
||||
|
||||
/**
|
||||
* Must match {@link #ACCEPTED_PATTERNS} RegEx. Signifies characters which result in a nested lookup via OGNL.
|
||||
*/
|
||||
public static final Set<Character> NESTING_CHARS = unmodifiableSet(new HashSet<>(asList('.', '[', '(')));
|
||||
public static final String NESTING_CHARS_STR = NESTING_CHARS.stream().map(String::valueOf).collect(joining());
|
||||
|
||||
public static final String[] DMI_AWARE_ACCEPTED_PATTERNS = {
|
||||
"\\w+([:]?\\w+)?((\\.\\w+)|(\\[\\d+])|(\\(\\d+\\))|(\\['(\\w-?|[\\u4e00-\\u9fa5]-?)+'])|(\\('(\\w-?|[\\u4e00-\\u9fa5]-?)+'\\)))*([!]?\\w+)?"
|
||||
};
|
||||
@@ -74,7 +82,7 @@ public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
newAcceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
} finally {
|
||||
acceptedPatterns = Collections.unmodifiableSet(newAcceptedPatterns);
|
||||
acceptedPatterns = unmodifiableSet(newAcceptedPatterns);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +93,7 @@ public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
|
||||
@Override
|
||||
public void setAcceptedPatterns(String[] additionalPatterns) {
|
||||
setAcceptedPatterns(new HashSet<>(Arrays.asList(additionalPatterns)));
|
||||
setAcceptedPatterns(new HashSet<>(asList(additionalPatterns)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,7 +105,7 @@ public class DefaultAcceptedPatternsChecker implements AcceptedPatternsChecker {
|
||||
newAcceptedPatterns.add(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
|
||||
}
|
||||
} finally {
|
||||
acceptedPatterns = Collections.unmodifiableSet(newAcceptedPatterns);
|
||||
acceptedPatterns = unmodifiableSet(newAcceptedPatterns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,21 +140,28 @@ public final class StrutsConstants {
|
||||
public static final String STRUTS_UI_ESCAPE_HTML_BODY = "struts.ui.escapeHtmlBody";
|
||||
|
||||
/** The maximum size of a multipart request (file upload) */
|
||||
public static final String STRUTS_MULTIPART_MAXSIZE = "struts.multipart.maxSize";
|
||||
public static final String STRUTS_MULTIPART_MAX_SIZE = "struts.multipart.maxSize";
|
||||
|
||||
/** The maximum size of all uploaded files.
|
||||
Used only with {@link org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest} */
|
||||
public static final String STRUTS_MULTIPART_MAX_SIZE_OF_FILES = "struts.multipart.maxSizeOfFiles";
|
||||
|
||||
/** The maximum number of files allowed in a multipart request */
|
||||
public static final String STRUTS_MULTIPART_MAXFILES = "struts.multipart.maxFiles";
|
||||
public static final String STRUTS_MULTIPART_MAX_FILES = "struts.multipart.maxFiles";
|
||||
|
||||
/** The maximum length of a string parameter in a multipart request. */
|
||||
public static final String STRUTS_MULTIPART_MAX_STRING_LENGTH = "struts.multipart.maxStringLength";
|
||||
|
||||
/** The maximum size per file in a multipart request */
|
||||
public static final String STRUTS_MULTIPART_MAXFILESIZE = "struts.multipart.maxFileSize";
|
||||
/** The directory to use for storing uploaded files */
|
||||
public static final String STRUTS_MULTIPART_SAVEDIR = "struts.multipart.saveDir";
|
||||
public static final String STRUTS_MULTIPART_MAX_FILE_SIZE = "struts.multipart.maxFileSize";
|
||||
|
||||
/** Declares the buffer size to be used during streaming multipart content to disk. Used only with {@link org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest} */
|
||||
public static final String STRUTS_MULTIPART_BUFFERSIZE = "struts.multipart.bufferSize";
|
||||
/** The directory to use for storing uploaded files */
|
||||
public static final String STRUTS_MULTIPART_SAVE_DIR = "struts.multipart.saveDir";
|
||||
|
||||
/** Declares the buffer size to be used during streaming multipart content to disk.
|
||||
* Used only with {@link org.apache.struts2.dispatcher.multipart.JakartaStreamMultiPartRequest}
|
||||
*/
|
||||
public static final String STRUTS_MULTIPART_BUFFER_SIZE = "struts.multipart.bufferSize";
|
||||
|
||||
/**
|
||||
* The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation
|
||||
@@ -469,6 +476,9 @@ public final class StrutsConstants {
|
||||
public static final String STRUTS_ADDITIONAL_EXCLUDED_PATTERNS = "struts.additional.excludedPatterns";
|
||||
public static final String STRUTS_ADDITIONAL_ACCEPTED_PATTERNS = "struts.additional.acceptedPatterns";
|
||||
|
||||
public static final String STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS = "struts.parameters.requireAnnotations";
|
||||
public static final String STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS_TRANSITION = "struts.parameters.requireAnnotations.transitionMode";
|
||||
|
||||
public static final String STRUTS_CONTENT_TYPE_MATCHER = "struts.contentTypeMatcher";
|
||||
|
||||
public static final String STRUTS_SMI_METHOD_REGEX = "struts.strictMethodInvocation.methodRegex";
|
||||
|
||||
@@ -99,4 +99,10 @@ public class File extends UIBean {
|
||||
public void setSize(String size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
@StrutsTagAttribute(description="Ignored during file upload")
|
||||
public void setValue(String value) {
|
||||
// ignores provided value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
*/
|
||||
package org.apache.struts2.components;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.apache.struts2.util.MakeIterator;
|
||||
import org.apache.struts2.views.annotations.StrutsTag;
|
||||
import org.apache.struts2.views.annotations.StrutsTagAttribute;
|
||||
@@ -188,8 +190,8 @@ import java.util.List;
|
||||
* <!-- START SNIPPET: example6description -->
|
||||
*
|
||||
* <p>Another way to create a simple loop, similar to JSTL's
|
||||
* <c:forEach begin="..." end="..." ...> is to use some
|
||||
* OGNL magic, which provides some under-the-covers magic to
|
||||
* <c:forEach begin="..." end="..." ...> is to use some
|
||||
* OGNL magic, which provides some under-the-covers magic to
|
||||
* make 0-n loops trivial. This example also loops five times.</p>
|
||||
*
|
||||
* <!-- END SNIPPET: example6description -->
|
||||
@@ -237,11 +239,17 @@ public class IteratorComponent extends ContextBean {
|
||||
protected Integer end;
|
||||
protected String stepStr;
|
||||
protected Integer step;
|
||||
private ThreadAllowlist threadAllowlist;
|
||||
|
||||
public IteratorComponent(ValueStack stack) {
|
||||
super(stack);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
|
||||
this.threadAllowlist = threadAllowlist;
|
||||
}
|
||||
|
||||
public boolean start(Writer writer) {
|
||||
//Create an iterator status if the status attribute was set.
|
||||
if (statusAttr != null) {
|
||||
@@ -298,6 +306,7 @@ public class IteratorComponent extends ContextBean {
|
||||
if ((iterator != null) && iterator.hasNext()) {
|
||||
Object currentValue = iterator.next();
|
||||
stack.push(currentValue);
|
||||
threadAllowlist.allowClass(currentValue.getClass());
|
||||
|
||||
String var = getVar();
|
||||
|
||||
|
||||
@@ -199,12 +199,12 @@ public class ConstantConfig {
|
||||
map.put(StrutsConstants.STRUTS_UI_TEMPLATEDIR, uiTemplateDir);
|
||||
map.put(StrutsConstants.STRUTS_UI_THEME, uiTheme);
|
||||
map.put(StrutsConstants.STRUTS_UI_THEME_EXPANSION_TOKEN, uiThemeExpansionToken);
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAXSIZE, Objects.toString(multipartMaxSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAXFILES, Objects.toString(multipartMaxFiles, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAXFILESIZE, Objects.toString(multipartMaxFileSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAX_SIZE, Objects.toString(multipartMaxSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAX_FILES, Objects.toString(multipartMaxFiles, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAX_FILE_SIZE, Objects.toString(multipartMaxFileSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH, Objects.toString(multipartMaxStringLength, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_SAVEDIR, multipartSaveDir);
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_BUFFERSIZE, Objects.toString(multipartBufferSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_SAVE_DIR, multipartSaveDir);
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_BUFFER_SIZE, Objects.toString(multipartBufferSize, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_PARSER, beanConfToString(multipartParser));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_ENABLED, Objects.toString(multipartEnabled, null));
|
||||
map.put(StrutsConstants.STRUTS_MULTIPART_VALIDATION_REGEX, Objects.toString(multipartValidationRegex, null));
|
||||
|
||||
@@ -68,6 +68,7 @@ import org.apache.struts2.dispatcher.mapper.ActionMapper;
|
||||
import org.apache.struts2.dispatcher.mapper.ActionMapping;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.apache.struts2.util.ObjectFactoryDestroyable;
|
||||
import org.apache.struts2.util.fs.JBossFileManager;
|
||||
|
||||
@@ -147,7 +148,7 @@ public class Dispatcher {
|
||||
private String defaultLocale;
|
||||
|
||||
/**
|
||||
* Store state of StrutsConstants.STRUTS_MULTIPART_SAVEDIR setting.
|
||||
* Store state of {@link StrutsConstants#STRUTS_MULTIPART_SAVE_DIR} setting.
|
||||
*/
|
||||
private String multipartSaveDir;
|
||||
|
||||
@@ -199,6 +200,7 @@ public class Dispatcher {
|
||||
private LocaleProviderFactory localeProviderFactory;
|
||||
private StaticContentLoader staticContentLoader;
|
||||
private ActionMapper actionMapper;
|
||||
private ThreadAllowlist threadAllowlist;
|
||||
|
||||
/**
|
||||
* Provide the dispatcher instance for the current thread.
|
||||
@@ -315,7 +317,7 @@ public class Dispatcher {
|
||||
*
|
||||
* @param val New setting
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_SAVEDIR)
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_SAVE_DIR)
|
||||
public void setMultipartSaveDir(String val) {
|
||||
multipartSaveDir = val;
|
||||
}
|
||||
@@ -323,7 +325,7 @@ public class Dispatcher {
|
||||
/**
|
||||
* @deprecated since 6.4.0, no replacement.
|
||||
*/
|
||||
@Deprecated(since = "6.4.9", forRemoval = true)
|
||||
@Deprecated(since = "6.4.0", forRemoval = true)
|
||||
public void setMultipartHandler(String val) {
|
||||
// no-op
|
||||
}
|
||||
@@ -404,6 +406,11 @@ public class Dispatcher {
|
||||
return actionMapper;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
|
||||
this.threadAllowlist = threadAllowlist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all instances bound to this dispatcher instance.
|
||||
*/
|
||||
@@ -868,11 +875,12 @@ public class Dispatcher {
|
||||
* @return the path to save uploaded files to
|
||||
*/
|
||||
protected String getSaveDir() {
|
||||
String saveDir = multipartSaveDir.trim();
|
||||
String saveDir = Objects.toString(multipartSaveDir, "").trim();
|
||||
|
||||
if (saveDir.equals("")) {
|
||||
File tempdir = (File) servletContext.getAttribute("jakarta.servlet.context.tempdir");
|
||||
LOG.info("Unable to find 'struts.multipart.saveDir' property setting. Defaulting to jakarta.servlet.context.tempdir");
|
||||
if (saveDir.isEmpty()) {
|
||||
File tempdir = (File) servletContext.getAttribute(ServletContext.TEMPDIR);
|
||||
LOG.info("Unable to find: {} property setting. Defaulting to: {}",
|
||||
StrutsConstants.STRUTS_MULTIPART_SAVE_DIR, ServletContext.TEMPDIR);
|
||||
|
||||
if (tempdir != null) {
|
||||
saveDir = tempdir.toString();
|
||||
@@ -885,9 +893,9 @@ public class Dispatcher {
|
||||
if (!multipartSaveDir.mkdirs()) {
|
||||
String logMessage;
|
||||
try {
|
||||
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'.";
|
||||
logMessage = "Could not create multipart save directory '" + multipartSaveDir.getCanonicalPath() + "'.";
|
||||
} catch (IOException e) {
|
||||
logMessage = "Could not find create multipart save directory '" + multipartSaveDir.toString() + "'.";
|
||||
logMessage = "Could not create multipart save directory '" + multipartSaveDir + "'.";
|
||||
}
|
||||
if (devMode) {
|
||||
LOG.error(logMessage);
|
||||
@@ -979,10 +987,12 @@ public class Dispatcher {
|
||||
public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException {
|
||||
// don't wrap more than once
|
||||
if (request instanceof StrutsRequestWrapper) {
|
||||
LOG.debug("Request already wrapped with: {}", StrutsRequestWrapper.class.getSimpleName());
|
||||
return request;
|
||||
}
|
||||
|
||||
if (isMultipartSupportEnabled(request) && isMultipartRequest(request)) {
|
||||
LOG.debug("Wrapping multipart request with: {}", MultiPartRequestWrapper.class.getSimpleName());
|
||||
request = new MultiPartRequestWrapper(
|
||||
getMultiPartRequest(),
|
||||
request,
|
||||
@@ -991,6 +1001,7 @@ public class Dispatcher {
|
||||
disableRequestAttributeValueStackLookup
|
||||
);
|
||||
} else {
|
||||
LOG.debug("Wrapping request using: {}", StrutsRequestWrapper.class.getSimpleName());
|
||||
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
|
||||
}
|
||||
|
||||
@@ -1005,6 +1016,7 @@ public class Dispatcher {
|
||||
* @since 2.5.11
|
||||
*/
|
||||
protected boolean isMultipartSupportEnabled(HttpServletRequest request) {
|
||||
LOG.debug("Support for multipart request is enabled: {}", multipartSupportEnabled);
|
||||
return multipartSupportEnabled;
|
||||
}
|
||||
|
||||
@@ -1019,9 +1031,12 @@ public class Dispatcher {
|
||||
String httpMethod = request.getMethod();
|
||||
String contentType = request.getContentType();
|
||||
|
||||
return REQUEST_POST_METHOD.equalsIgnoreCase(httpMethod) &&
|
||||
contentType != null &&
|
||||
multipartValidationPattern.matcher(contentType.toLowerCase(Locale.ENGLISH)).matches();
|
||||
boolean isPostRequest = REQUEST_POST_METHOD.equalsIgnoreCase(httpMethod);
|
||||
boolean isProperContentType = contentType != null && multipartValidationPattern.matcher(contentType.toLowerCase(Locale.ENGLISH)).matches();
|
||||
|
||||
LOG.debug("Validating if this is a proper Multipart request. Request is POST: {} and ContentType matches pattern ({}): {}",
|
||||
isPostRequest, multipartValidationPattern, isProperContentType);
|
||||
return isPostRequest && isProperContentType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1042,6 +1057,7 @@ public class Dispatcher {
|
||||
*/
|
||||
public void cleanUpRequest(HttpServletRequest request) {
|
||||
ContainerHolder.clear();
|
||||
threadAllowlist.clearAllowlist();
|
||||
if (!(request instanceof MultiPartRequestWrapper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+260
-31
@@ -18,17 +18,29 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import com.opensymphony.xwork2.LocaleProviderFactory;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadContentTypeException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadSizeException;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.commons.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 jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract class with some helper methods, it should be used
|
||||
@@ -46,7 +58,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
public static final int BUFFER_SIZE = 10240;
|
||||
|
||||
/**
|
||||
* Internal list of raised errors to be passed to the the Struts2 framework.
|
||||
* Internal list of raised errors to be passed to the Struts2 framework.
|
||||
*/
|
||||
protected List<LocalizedMessage> errors = new ArrayList<>();
|
||||
|
||||
@@ -55,6 +67,11 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
*/
|
||||
protected Long maxSize;
|
||||
|
||||
/**
|
||||
* Specifies the maximum size of all the uploaded files.
|
||||
*/
|
||||
protected Long maxSizeOfFiles;
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of files in one request.
|
||||
*/
|
||||
@@ -66,7 +83,7 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
protected Long maxStringLength;
|
||||
|
||||
/**
|
||||
* Specifies the maximum size per file in the request.
|
||||
* Specifies the maximum size per a file in the request.
|
||||
*/
|
||||
protected Long maxFileSize;
|
||||
|
||||
@@ -75,17 +92,25 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
*/
|
||||
protected int bufferSize = BUFFER_SIZE;
|
||||
|
||||
/**
|
||||
* Defines default encoding to encode data from request used if not provided with request
|
||||
*/
|
||||
protected String defaultEncoding;
|
||||
|
||||
/**
|
||||
* Localization to be used regarding errors.
|
||||
* Map between file fields and file data.
|
||||
*/
|
||||
protected Locale defaultLocale = Locale.ENGLISH;
|
||||
protected Map<String, List<UploadedFile>> uploadedFiles = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Map between non-file fields and values.
|
||||
*/
|
||||
protected Map<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @param bufferSize Sets the buffer size to be used.
|
||||
*/
|
||||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_BUFFERSIZE, required = false)
|
||||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_BUFFER_SIZE, required = false)
|
||||
public void setBufferSize(String bufferSize) {
|
||||
this.bufferSize = Integer.parseInt(bufferSize);
|
||||
}
|
||||
@@ -98,60 +123,160 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
/**
|
||||
* @param maxSize Injects the Struts multipart request maximum size.
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE)
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_MAX_SIZE)
|
||||
public void setMaxSize(String maxSize) {
|
||||
this.maxSize = Long.parseLong(maxSize);
|
||||
}
|
||||
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_MAXFILES)
|
||||
/**
|
||||
* @param maxSizeOfFiles Injects the Struts maximum size of all uploaded files.
|
||||
*/
|
||||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_MAX_SIZE_OF_FILES, required = false)
|
||||
public void setMaxSizeOfFiles(String maxSizeOfFiles) {
|
||||
this.maxSizeOfFiles = Long.parseLong(maxSizeOfFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxFiles Injects the Struts maximum size of an individual file uploaded.
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_MAX_FILES)
|
||||
public void setMaxFiles(String maxFiles) {
|
||||
this.maxFiles = Long.parseLong(maxFiles);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_MAXFILESIZE, required = false)
|
||||
/**
|
||||
* @param maxFileSize Injects the Struts maximum number of files, which can be uploaded.
|
||||
*/
|
||||
@Inject(value = StrutsConstants.STRUTS_MULTIPART_MAX_FILE_SIZE, required = false)
|
||||
public void setMaxFileSize(String maxFileSize) {
|
||||
this.maxFileSize = Long.parseLong(maxFileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxStringLength Injects the Struts maximum size of single form field.
|
||||
*/
|
||||
@Inject(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH)
|
||||
public void setMaxStringLength(String maxStringLength) {
|
||||
this.maxStringLength = Long.parseLong(maxStringLength);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
|
||||
defaultLocale = localeProviderFactory.createLocaleProvider().getLocale();
|
||||
/**
|
||||
* Process the request extract file upload data
|
||||
*
|
||||
* @param request current {@link HttpServletRequest}
|
||||
* @param saveDir a temporary directory to store files
|
||||
*/
|
||||
protected abstract void processUpload(HttpServletRequest request, String saveDir) throws IOException;
|
||||
|
||||
/**
|
||||
* @param request multipart request
|
||||
* @return character encoding from request or {@link #defaultEncoding}
|
||||
*/
|
||||
protected Charset readCharsetEncoding(HttpServletRequest request) {
|
||||
String charsetStr = StringUtils.isBlank(request.getCharacterEncoding())
|
||||
? defaultEncoding
|
||||
: request.getCharacterEncoding();
|
||||
|
||||
return Charset.forName(charsetStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request Inspect the servlet request and set the locale if one wasn't provided by
|
||||
* the Struts2 framework.
|
||||
* Creates an instance of {@link JakartaServletDiskFileUpload} used by the parser to extract uploaded files
|
||||
*
|
||||
* @param charset used charset from incoming request
|
||||
* @param saveDir a temporary folder to store uploaded files (not always needed)
|
||||
*/
|
||||
protected void setLocale(HttpServletRequest request) {
|
||||
if (defaultLocale == null) {
|
||||
defaultLocale = request.getLocale();
|
||||
protected abstract JakartaServletDiskFileUpload createJakartaFileUpload(Charset charset, Path saveDir);
|
||||
|
||||
protected JakartaServletDiskFileUpload prepareServletFileUpload(Charset charset, Path saveDir) {
|
||||
JakartaServletDiskFileUpload servletFileUpload = createJakartaFileUpload(charset, saveDir);
|
||||
|
||||
if (maxSize != null) {
|
||||
LOG.debug("Applies max size: {} to file upload request", maxSize);
|
||||
servletFileUpload.setSizeMax(maxSize);
|
||||
}
|
||||
if (maxFiles != null) {
|
||||
LOG.debug("Applies max files number: {} to file upload request", maxFiles);
|
||||
servletFileUpload.setFileCountMax(maxFiles);
|
||||
}
|
||||
if (maxFileSize != null) {
|
||||
LOG.debug("Applies max size of single file: {} to file upload request", maxFileSize);
|
||||
servletFileUpload.setFileSizeMax(maxFileSize);
|
||||
}
|
||||
return servletFileUpload;
|
||||
}
|
||||
|
||||
protected boolean exceedsMaxStringLength(String fieldName, String fieldValue) {
|
||||
if (maxStringLength != null && fieldValue.length() > maxStringLength) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Form field: {} of size: {} bytes exceeds limit of: {}.",
|
||||
sanitizeNewlines(fieldName), fieldValue.length(), maxStringLength);
|
||||
}
|
||||
LocalizedMessage localizedMessage = new LocalizedMessage(this.getClass(),
|
||||
STRUTS_MESSAGES_UPLOAD_ERROR_PARAMETER_TOO_LONG_KEY, null,
|
||||
new Object[]{fieldName, maxStringLength, fieldValue.length()});
|
||||
if (!errors.contains(localizedMessage)) {
|
||||
errors.add(localizedMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the upload.
|
||||
*
|
||||
* @param request the servlet request
|
||||
* @param saveDir location of the save dir
|
||||
*/
|
||||
public void parse(HttpServletRequest request, String saveDir) throws IOException {
|
||||
try {
|
||||
processUpload(request, saveDir);
|
||||
} catch (FileUploadException e) {
|
||||
LOG.debug("Error parsing the multi-part request!", e);
|
||||
Class<? extends Throwable> exClass = FileUploadException.class;
|
||||
Object[] args = new Object[]{};
|
||||
|
||||
if (e instanceof FileUploadByteCountLimitException ex) {
|
||||
exClass = ex.getClass();
|
||||
args = new Object[]{ex.getFieldName(), ex.getFileName(), ex.getPermitted(), ex.getActualSize()};
|
||||
} else if (e instanceof FileUploadFileCountLimitException ex) {
|
||||
exClass = ex.getClass();
|
||||
args = new Object[]{ex.getPermitted(), ex.getActualSize()};
|
||||
} else if (e instanceof FileUploadSizeException ex) {
|
||||
exClass = ex.getClass();
|
||||
args = new Object[]{ex.getPermitted(), ex.getActualSize()};
|
||||
} else if (e instanceof FileUploadContentTypeException ex) {
|
||||
exClass = ex.getClass();
|
||||
args = new Object[]{ex.getContentType()};
|
||||
}
|
||||
|
||||
LocalizedMessage errorMessage = buildErrorMessage(exClass, e.getMessage(), args);
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Unable to parse request", e);
|
||||
LocalizedMessage errorMessage = buildErrorMessage(e.getClass(), e.getMessage(), new Object[]{});
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build error message.
|
||||
*
|
||||
* @param e the Throwable/Exception
|
||||
* @param args arguments
|
||||
* @param exceptionClass a class of the exception
|
||||
* @param defaultMessage a default message to use
|
||||
* @param args arguments
|
||||
* @return error message
|
||||
*/
|
||||
protected LocalizedMessage buildErrorMessage(Throwable e, Object[] args) {
|
||||
String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName();
|
||||
protected LocalizedMessage buildErrorMessage(Class<? extends Throwable> exceptionClass, String defaultMessage, Object[] args) {
|
||||
String errorKey = "struts.messages.upload.error." + exceptionClass.getSimpleName();
|
||||
LOG.debug("Preparing error message for key: [{}]", errorKey);
|
||||
|
||||
return new LocalizedMessage(this.getClass(), errorKey, e.getMessage(), args);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
|
||||
*/
|
||||
public List<LocalizedMessage> getErrors() {
|
||||
return errors;
|
||||
return new LocalizedMessage(this.getClass(), errorKey, defaultMessage, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,4 +296,108 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
protected String sanitizeNewlines(String before) {
|
||||
return before.replaceAll("\\R", "_");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
|
||||
*/
|
||||
public List<LocalizedMessage> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getFileParameterNames() {
|
||||
return Collections.enumeration(uploadedFiles.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
|
||||
*/
|
||||
public String[] getContentType(String fieldName) {
|
||||
return uploadedFiles.getOrDefault(fieldName, Collections.emptyList()).stream()
|
||||
.map(UploadedFile::getContentType)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
|
||||
*/
|
||||
public UploadedFile[] getFile(String fieldName) {
|
||||
return uploadedFiles.getOrDefault(fieldName, Collections.emptyList())
|
||||
.toArray(UploadedFile[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
|
||||
*/
|
||||
public String[] getFileNames(String fieldName) {
|
||||
return uploadedFiles.getOrDefault(fieldName, Collections.emptyList()).stream()
|
||||
.map(file -> getCanonicalName(file.getOriginalName()))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
|
||||
*/
|
||||
public String[] getFilesystemName(String fieldName) {
|
||||
return uploadedFiles.getOrDefault(fieldName, Collections.emptyList()).stream()
|
||||
.map(UploadedFile::getAbsolutePath)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
|
||||
*/
|
||||
public String getParameter(String name) {
|
||||
List<String> paramValue = parameters.getOrDefault(name, Collections.emptyList());
|
||||
if (!paramValue.isEmpty()) {
|
||||
return paramValue.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(parameters.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
|
||||
*/
|
||||
public String[] getParameterValues(String name) {
|
||||
return parameters.getOrDefault(name, Collections.emptyList())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
|
||||
*/
|
||||
public void cleanUp() {
|
||||
try {
|
||||
LOG.debug("Performing File Upload temporary storage cleanup.");
|
||||
for (List<UploadedFile> uploadedFileList : uploadedFiles.values()) {
|
||||
for (UploadedFile uploadedFile : uploadedFileList) {
|
||||
if (uploadedFile.isFile()) {
|
||||
LOG.debug("Deleting file: {}", uploadedFile.getName());
|
||||
if (!uploadedFile.delete()) {
|
||||
LOG.warn("There was a problem attempting to delete file: {}", uploadedFile.getName());
|
||||
}
|
||||
} else {
|
||||
LOG.debug("File: {} already deleted", uploadedFile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
uploadedFiles = new HashMap<>();
|
||||
parameters = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+66
-334
@@ -21,375 +21,107 @@ package org.apache.struts2.dispatcher.multipart;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.fileupload2.core.DiskFileItem;
|
||||
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
|
||||
import org.apache.commons.fileupload2.core.FileItem;
|
||||
import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadContentTypeException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadSizeException;
|
||||
import org.apache.commons.fileupload2.core.RequestContext;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Multipart form data request adapter for Jakarta Commons Fileupload package.
|
||||
* Multipart form data request adapter for Jakarta Commons FileUpload package.
|
||||
*/
|
||||
public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
|
||||
|
||||
static final Logger LOG = LogManager.getLogger(JakartaMultiPartRequest.class);
|
||||
|
||||
// maps parameter name -> List of FileItem objects
|
||||
protected Map<String, List<FileItem>> files = new HashMap<>();
|
||||
|
||||
// maps parameter name -> List of param values
|
||||
protected Map<String, List<String>> params = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new request wrapper to handle multipart data using methods adapted from Jason Pell's
|
||||
* multipart classes (see class description).
|
||||
*
|
||||
* @param saveDir the directory to save off the file
|
||||
* @param request the request containing the multipart
|
||||
* @throws java.io.IOException is thrown if encoding fails.
|
||||
*/
|
||||
public void parse(HttpServletRequest request, String saveDir) throws IOException {
|
||||
try {
|
||||
setLocale(request);
|
||||
processUpload(request, saveDir);
|
||||
} catch (FileUploadException e) {
|
||||
LOG.debug("Request exceeded size limit!", e);
|
||||
LocalizedMessage errorMessage;
|
||||
if (e instanceof FileUploadByteCountLimitException) {
|
||||
FileUploadByteCountLimitException ex = (FileUploadByteCountLimitException) e;
|
||||
errorMessage = buildErrorMessage(e, new Object[]{
|
||||
ex.getFieldName(), ex.getFileName(), ex.getPermitted(), ex.getActualSize()
|
||||
});
|
||||
} else if (e instanceof FileUploadFileCountLimitException) {
|
||||
FileUploadFileCountLimitException ex = (FileUploadFileCountLimitException) e;
|
||||
errorMessage = buildErrorMessage(e, new Object[]{
|
||||
ex.getPermitted(), ex.getActualSize()
|
||||
});
|
||||
} else if (e instanceof FileUploadSizeException) {
|
||||
FileUploadSizeException ex = (FileUploadSizeException) e;
|
||||
errorMessage = buildErrorMessage(e, new Object[]{
|
||||
ex.getPermitted(), ex.getActualSize()
|
||||
});
|
||||
} else if (e instanceof FileUploadContentTypeException) {
|
||||
FileUploadContentTypeException ex = (FileUploadContentTypeException) e;
|
||||
errorMessage = buildErrorMessage(e, new Object[]{
|
||||
ex.getContentType()
|
||||
});
|
||||
} else {
|
||||
errorMessage = buildErrorMessage(e, new Object[]{});
|
||||
}
|
||||
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Unable to parse request", e);
|
||||
LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final Logger LOG = LogManager.getLogger(JakartaMultiPartRequest.class);
|
||||
|
||||
@Override
|
||||
protected void processUpload(HttpServletRequest request, String saveDir) throws IOException {
|
||||
Charset charset = readCharsetEncoding(request);
|
||||
|
||||
if (JakartaServletFileUpload.isMultipartContent(request)) {
|
||||
for (FileItem item : parseRequest(request, saveDir)) {
|
||||
LOG.debug("Found file item: [{}]", sanitizeNewlines(item.getFieldName()));
|
||||
if (item.isFormField()) {
|
||||
processNormalFormField(item, request.getCharacterEncoding());
|
||||
} else {
|
||||
processFileField(item);
|
||||
}
|
||||
JakartaServletDiskFileUpload servletFileUpload =
|
||||
prepareServletFileUpload(charset, Path.of(saveDir));
|
||||
|
||||
for (DiskFileItem item : servletFileUpload.parseRequest(request)) {
|
||||
LOG.debug(() -> "Processing a form field: " + sanitizeNewlines(item.getFieldName()));
|
||||
if (item.isFormField()) {
|
||||
processNormalFormField(item, charset);
|
||||
} else {
|
||||
LOG.debug(() -> "Processing a file: " + sanitizeNewlines(item.getFieldName()));
|
||||
processFileField(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processFileField(FileItem item) {
|
||||
LOG.debug("Item is a file upload");
|
||||
protected JakartaServletDiskFileUpload createJakartaFileUpload(Charset charset, Path saveDir) {
|
||||
DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder();
|
||||
|
||||
// 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: {}", sanitizeNewlines(item.getFieldName()));
|
||||
return;
|
||||
}
|
||||
LOG.debug("Using file save directory: {}", saveDir);
|
||||
builder.setPath(saveDir);
|
||||
|
||||
List<FileItem> values;
|
||||
if (files.get(item.getFieldName()) != null) {
|
||||
values = files.get(item.getFieldName());
|
||||
LOG.debug("Sets minimal buffer size to always write file to disk");
|
||||
builder.setBufferSize(1);
|
||||
|
||||
LOG.debug("Using charset: {}", charset);
|
||||
builder.setCharset(charset);
|
||||
|
||||
DiskFileItemFactory factory = builder.get();
|
||||
return new JakartaServletDiskFileUpload(factory);
|
||||
}
|
||||
|
||||
protected void processNormalFormField(DiskFileItem item, Charset charset) throws IOException {
|
||||
LOG.debug("Item: {} is a normal form field", item.getName());
|
||||
|
||||
List<String> values;
|
||||
String fieldName = item.getFieldName();
|
||||
if (parameters.get(fieldName) != null) {
|
||||
values = parameters.get(fieldName);
|
||||
} else {
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
|
||||
values.add(item);
|
||||
files.put(item.getFieldName(), values);
|
||||
String fieldValue = item.getString(charset);
|
||||
if (exceedsMaxStringLength(fieldName, fieldValue)) {
|
||||
return;
|
||||
}
|
||||
if (item.getSize() == 0) {
|
||||
values.add(StringUtils.EMPTY);
|
||||
} else {
|
||||
values.add(fieldValue);
|
||||
}
|
||||
parameters.put(fieldName, values);
|
||||
}
|
||||
|
||||
protected void processNormalFormField(FileItem item, String charset) throws IOException {
|
||||
try {
|
||||
LOG.debug("Item is a normal form field");
|
||||
Charset encoding = Charset.forName(charset);
|
||||
|
||||
List<String> values;
|
||||
if (params.get(item.getFieldName()) != null) {
|
||||
values = params.get(item.getFieldName());
|
||||
} else {
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
|
||||
long size = item.getSize();
|
||||
if (size > maxStringLength) {
|
||||
LOG.debug("Form field {} of size {} bytes exceeds limit of {}.", sanitizeNewlines(item.getFieldName()), size, maxStringLength);
|
||||
LocalizedMessage localizedMessage = new LocalizedMessage(this.getClass(),
|
||||
STRUTS_MESSAGES_UPLOAD_ERROR_PARAMETER_TOO_LONG_KEY, null,
|
||||
new Object[]{item.getFieldName(), maxStringLength, size});
|
||||
if (!errors.contains(localizedMessage)) {
|
||||
errors.add(localizedMessage);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (size == 0) {
|
||||
values.add(StringUtils.EMPTY);
|
||||
} else if (charset == null) {
|
||||
values.add(item.getString()); // WW-633
|
||||
} else {
|
||||
values.add(item.getString(encoding));
|
||||
}
|
||||
params.put(item.getFieldName(), values);
|
||||
} finally {
|
||||
item.delete();
|
||||
}
|
||||
}
|
||||
|
||||
protected List<FileItem> parseRequest(HttpServletRequest servletRequest, String saveDir) throws FileUploadException {
|
||||
DiskFileItemFactory fac = createDiskFileItemFactory(saveDir);
|
||||
JakartaServletFileUpload upload = createServletFileUpload(fac);
|
||||
|
||||
|
||||
return upload.parseRequest(createRequestContext(servletRequest));
|
||||
}
|
||||
|
||||
protected JakartaServletFileUpload createServletFileUpload(DiskFileItemFactory fac) {
|
||||
JakartaServletFileUpload upload = new JakartaServletFileUpload(fac);
|
||||
if (maxSize != null) {
|
||||
upload.setSizeMax(maxSize);
|
||||
}
|
||||
if (maxFiles != null) {
|
||||
upload.setFileCountMax(maxFiles);
|
||||
}
|
||||
if (maxFileSize != null) {
|
||||
upload.setFileSizeMax(maxFileSize);
|
||||
}
|
||||
return upload;
|
||||
}
|
||||
|
||||
protected DiskFileItemFactory createDiskFileItemFactory(String saveDir) {
|
||||
DiskFileItemFactory.Builder fac = DiskFileItemFactory.builder();
|
||||
// Make sure that the data is written to file, even if the file is empty.
|
||||
//setting 0 or -1 no longer seems to work for fileupload buffer size, so using 1 instead.
|
||||
fac.setBufferSize(1);
|
||||
if (saveDir != null) {
|
||||
fac.setPath(saveDir);
|
||||
}
|
||||
return fac.get();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getFileParameterNames() {
|
||||
return Collections.enumeration(files.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
|
||||
*/
|
||||
public String[] getContentType(String fieldName) {
|
||||
List<FileItem> items = files.get(fieldName);
|
||||
|
||||
if (items == null) {
|
||||
return null;
|
||||
protected void processFileField(DiskFileItem item) {
|
||||
// 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: " + sanitizeNewlines(item.getFieldName()));
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> contentTypes = new ArrayList<>(items.size());
|
||||
for (FileItem fileItem : items) {
|
||||
contentTypes.add(fileItem.getContentType());
|
||||
List<UploadedFile> values;
|
||||
if (uploadedFiles.get(item.getFieldName()) != null) {
|
||||
values = uploadedFiles.get(item.getFieldName());
|
||||
} else {
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
|
||||
return contentTypes.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
|
||||
*/
|
||||
public UploadedFile[] getFile(String fieldName) {
|
||||
List<FileItem> items = files.get(fieldName);
|
||||
|
||||
if (items == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<UploadedFile> fileList = new ArrayList<>(items.size());
|
||||
for (FileItem fileItem : items) {
|
||||
DiskFileItem diskFileItem = (DiskFileItem) fileItem;
|
||||
File storeLocation = diskFileItem.getPath().toFile();
|
||||
|
||||
// Ensure file exists even if it is empty.
|
||||
if (diskFileItem.getSize() == 0 && !storeLocation.exists()) {
|
||||
try {
|
||||
storeLocation.createNewFile();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Cannot write uploaded empty file to disk: {}", storeLocation.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(storeLocation)
|
||||
.withContentType(fileItem.getContentType())
|
||||
.withOriginalName(fileItem.getName())
|
||||
if (item.isInMemory()) {
|
||||
LOG.warn("Storing uploaded files just in memory isn't supported currently, skipping file: {}!", item.getName());
|
||||
} else {
|
||||
UploadedFile uploadedFile = StrutsUploadedFile.Builder
|
||||
.create(item.getPath().toFile())
|
||||
.withOriginalName(item.getName())
|
||||
.withContentType(item.getContentType())
|
||||
.build();
|
||||
fileList.add(uploadedFile);
|
||||
values.add(uploadedFile);
|
||||
}
|
||||
|
||||
return fileList.toArray(new UploadedFile[0]);
|
||||
uploadedFiles.put(item.getFieldName(), values);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
|
||||
*/
|
||||
public String[] getFileNames(String fieldName) {
|
||||
List<FileItem> items = files.get(fieldName);
|
||||
|
||||
if (items == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> fileNames = new ArrayList<>(items.size());
|
||||
for (FileItem fileItem : items) {
|
||||
fileNames.add(getCanonicalName(fileItem.getName()));
|
||||
}
|
||||
|
||||
return fileNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
|
||||
*/
|
||||
public String[] getFilesystemName(String fieldName) {
|
||||
List<FileItem> items = files.get(fieldName);
|
||||
|
||||
if (items == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> fileNames = new ArrayList<>(items.size());
|
||||
for (FileItem fileItem : items) {
|
||||
fileNames.add(((DiskFileItem) fileItem).getPath().toFile().getName());
|
||||
}
|
||||
|
||||
return fileNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
|
||||
*/
|
||||
public String getParameter(String name) {
|
||||
List<String> v = params.get(name);
|
||||
if (v != null && !v.isEmpty()) {
|
||||
return v.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(params.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
|
||||
*/
|
||||
public String[] getParameterValues(String name) {
|
||||
List<String> v = params.get(name);
|
||||
if (v != null && !v.isEmpty()) {
|
||||
return v.toArray(new String[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a RequestContext needed by Jakarta Commons Upload.
|
||||
*
|
||||
* @param req the request.
|
||||
* @return a new request context.
|
||||
*/
|
||||
protected RequestContext createRequestContext(final HttpServletRequest req) {
|
||||
return new RequestContext() {
|
||||
public String getCharacterEncoding() {
|
||||
return req.getCharacterEncoding();
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return req.getContentType();
|
||||
}
|
||||
|
||||
public long getContentLength() {
|
||||
return req.getContentLength();
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
InputStream in = req.getInputStream();
|
||||
if (in == null) {
|
||||
throw new IOException("Missing content in the request");
|
||||
}
|
||||
return req.getInputStream();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @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 {} {}", name, item);
|
||||
if (!item.isInMemory()) {
|
||||
try {
|
||||
item.delete();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String sanitizeNewlines(String before) {
|
||||
return before.replaceAll("[\n\r]", "_");
|
||||
}
|
||||
}
|
||||
|
||||
+165
-384
@@ -18,30 +18,28 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import org.apache.commons.fileupload2.core.DiskFileItem;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.fileupload2.core.DiskFileItemFactory;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.commons.fileupload2.core.FileUploadSizeException;
|
||||
import org.apache.commons.fileupload2.core.FileItemInputIterator;
|
||||
import org.apache.commons.fileupload2.core.FileItemInput;
|
||||
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
|
||||
import org.apache.commons.fileupload2.core.FileUploadSizeException;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -54,153 +52,7 @@ import java.util.UUID;
|
||||
*/
|
||||
public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
|
||||
static final Logger LOG = LogManager.getLogger(JakartaStreamMultiPartRequest.class);
|
||||
|
||||
/**
|
||||
* Map between file fields and file data.
|
||||
*/
|
||||
protected Map<String, List<FileInfo>> fileInfos = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Map between non-file fields and values.
|
||||
*/
|
||||
protected Map<String, List<String>> parameters = new HashMap<>();
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
|
||||
*/
|
||||
public void cleanUp() {
|
||||
LOG.debug("Performing File Upload temporary storage cleanup.");
|
||||
for (List<FileInfo> fileInfoList : fileInfos.values()) {
|
||||
for (FileInfo fileInfo : fileInfoList) {
|
||||
File file = fileInfo.getFile();
|
||||
LOG.debug("Deleting file '{}'.", file.getName());
|
||||
if (!file.delete()) {
|
||||
LOG.warn("There was a problem attempting to delete file '{}'.", file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType(java.lang.String)
|
||||
*/
|
||||
public String[] getContentType(String fieldName) {
|
||||
List<FileInfo> infos = fileInfos.get(fieldName);
|
||||
if (infos == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> types = new ArrayList<>(infos.size());
|
||||
for (FileInfo fileInfo : infos) {
|
||||
types.add(fileInfo.getContentType());
|
||||
}
|
||||
|
||||
return types.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java.lang.String)
|
||||
*/
|
||||
public UploadedFile[] getFile(String fieldName) {
|
||||
List<FileInfo> infos = fileInfos.get(fieldName);
|
||||
if (infos == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return infos.stream().map(fileInfo ->
|
||||
StrutsUploadedFile.Builder.create(fileInfo.getFile())
|
||||
.withContentType(fileInfo.contentType)
|
||||
.withOriginalName(fileInfo.originalName)
|
||||
.build()
|
||||
).toArray(UploadedFile[]::new);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames(java.lang.String)
|
||||
*/
|
||||
public String[] getFileNames(String fieldName) {
|
||||
List<FileInfo> infos = fileInfos.get(fieldName);
|
||||
if (infos == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> names = new ArrayList<>(infos.size());
|
||||
for (FileInfo fileInfo : infos) {
|
||||
names.add(getCanonicalName(fileInfo.getOriginalName()));
|
||||
}
|
||||
|
||||
return names.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getFileParameterNames() {
|
||||
return Collections.enumeration(fileInfos.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName(java.lang.String)
|
||||
*/
|
||||
public String[] getFilesystemName(String fieldName) {
|
||||
List<FileInfo> infos = fileInfos.get(fieldName);
|
||||
if (infos == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> names = new ArrayList<>(infos.size());
|
||||
for (FileInfo fileInfo : infos) {
|
||||
names.add(fileInfo.getFile().getName());
|
||||
}
|
||||
|
||||
return names.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter(java.lang.String)
|
||||
*/
|
||||
public String getParameter(String name) {
|
||||
List<String> values = parameters.get(name);
|
||||
if (values != null && !values.isEmpty()) {
|
||||
return values.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames()
|
||||
*/
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(parameters.keySet());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues(java.lang.String)
|
||||
*/
|
||||
public String[] getParameterValues(String name) {
|
||||
List<String> values = parameters.get(name);
|
||||
if (values != null && !values.isEmpty()) {
|
||||
return values.toArray(new String[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#parse(jakarta.servlet.http.HttpServletRequest, java.lang.String)
|
||||
*/
|
||||
public void parse(HttpServletRequest request, String saveDir) throws IOException {
|
||||
try {
|
||||
setLocale(request);
|
||||
processUpload(request, saveDir);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error occurred during parsing of multi part request", e);
|
||||
LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final Logger LOG = LogManager.getLogger(JakartaStreamMultiPartRequest.class);
|
||||
|
||||
/**
|
||||
* Processes the upload.
|
||||
@@ -208,153 +60,149 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
* @param request the servlet request
|
||||
* @param saveDir location of the save dir
|
||||
*/
|
||||
protected void processUpload(HttpServletRequest request, String saveDir) throws Exception {
|
||||
@Override
|
||||
protected void processUpload(HttpServletRequest request, String saveDir) throws IOException {
|
||||
Charset charset = readCharsetEncoding(request);
|
||||
Path location = Path.of(saveDir);
|
||||
|
||||
// Sanity check that the request is a multi-part/form-data request.
|
||||
if (JakartaServletFileUpload.isMultipartContent(request)) {
|
||||
JakartaServletDiskFileUpload servletFileUpload =
|
||||
prepareServletFileUpload(charset, location);
|
||||
|
||||
// Sanity check on request size.
|
||||
boolean requestSizePermitted = isRequestSizePermitted(request);
|
||||
|
||||
// Interface with Commons FileUpload API
|
||||
// Using the Streaming API
|
||||
JakartaServletFileUpload<DiskFileItem, DiskFileItemFactory> servletFileUpload = new JakartaServletFileUpload<>();
|
||||
if (maxSize != null) {
|
||||
servletFileUpload.setSizeMax(maxSize);
|
||||
}
|
||||
if (maxFiles != null) {
|
||||
servletFileUpload.setFileCountMax(maxFiles);
|
||||
}
|
||||
if (maxFileSize != null) {
|
||||
servletFileUpload.setFileSizeMax(maxFileSize);
|
||||
}
|
||||
FileItemInputIterator i = servletFileUpload.getItemIterator(request);
|
||||
|
||||
// Iterate the file items
|
||||
while (i.hasNext()) {
|
||||
try {
|
||||
FileItemInput itemStream = i.next();
|
||||
|
||||
// If the file item stream is a form field, delegate to the
|
||||
// field item stream handler
|
||||
if (itemStream.isFormField()) {
|
||||
processFileItemStreamAsFormField(itemStream);
|
||||
}
|
||||
|
||||
// Delegate the file item stream for a file field to the
|
||||
// file item stream handler, but delegation is skipped
|
||||
// if the requestSizePermitted check failed based on the
|
||||
// complete content-size of the request.
|
||||
else {
|
||||
|
||||
// prevent processing file field item if request size not allowed.
|
||||
if (!requestSizePermitted) {
|
||||
addFileSkippedError(itemStream.getName(), request);
|
||||
LOG.debug("Skipped stream '{}', request maximum size ({}) exceeded.", itemStream.getName(), maxSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
processFileItemStreamAsFileField(itemStream, saveDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Error occurred during process upload", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines whether the request allowed based on content length.
|
||||
*
|
||||
* @param request the servlet request
|
||||
* @return true if request size is permitted
|
||||
*/
|
||||
protected boolean isRequestSizePermitted(HttpServletRequest request) {
|
||||
// if maxSize is specified as -1, there is no sanity check and it's
|
||||
// safe to return true for any request, delegating the failure
|
||||
// checks later in the upload process.
|
||||
if (maxSize == null || maxSize == -1 || request == null) {
|
||||
return true;
|
||||
}
|
||||
return request.getContentLength() < maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request the servlet request
|
||||
* @return the request content length.
|
||||
*/
|
||||
protected long getRequestSize(HttpServletRequest request) {
|
||||
return request != null ? request.getContentLength() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file skipped message notification for action messages.
|
||||
*
|
||||
* @param fileName file name
|
||||
* @param request the servlet request
|
||||
*/
|
||||
protected void addFileSkippedError(String fileName, HttpServletRequest request) {
|
||||
String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded.";
|
||||
long allowedMaxSize = maxSize != null ? maxSize : -1;
|
||||
FileUploadSizeException exception = new FileUploadSizeException(exceptionMessage, getRequestSize(request), allowedMaxSize);
|
||||
LocalizedMessage message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), allowedMaxSize});
|
||||
if (!errors.contains(message)) {
|
||||
errors.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the FileItemStream as a Form Field.
|
||||
*
|
||||
* @param itemStream file item stream
|
||||
*/
|
||||
protected void processFileItemStreamAsFormField(FileItemInput itemStream) {
|
||||
String fieldName = itemStream.getFieldName();
|
||||
try {
|
||||
List<String> values;
|
||||
|
||||
String fieldValue = itemStream.getInputStream().toString();
|
||||
if (!parameters.containsKey(fieldName)) {
|
||||
values = new ArrayList<>();
|
||||
parameters.put(fieldName, values);
|
||||
LOG.debug("Using Jakarta Stream API to process request");
|
||||
servletFileUpload.getItemIterator(request).forEachRemaining(item -> {
|
||||
if (item.isFormField()) {
|
||||
LOG.debug(() -> "Processing a form field: " + sanitizeNewlines(item.getFieldName()));
|
||||
processFileItemAsFormField(item);
|
||||
} else {
|
||||
values = parameters.get(fieldName);
|
||||
LOG.debug(() -> "Processing a file: " + sanitizeNewlines(item.getFieldName()));
|
||||
processFileItemAsFileField(item, location);
|
||||
}
|
||||
values.add(fieldValue);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to handle form field '{}'.", fieldName, e);
|
||||
});
|
||||
}
|
||||
|
||||
protected JakartaServletDiskFileUpload createJakartaFileUpload(Charset charset, Path location) {
|
||||
DiskFileItemFactory.Builder builder = DiskFileItemFactory.builder();
|
||||
|
||||
LOG.debug("Using file save directory: {}", location);
|
||||
builder.setPath(location);
|
||||
|
||||
LOG.debug("Sets buffer size: {}", bufferSize);
|
||||
builder.setBufferSize(bufferSize);
|
||||
|
||||
LOG.debug("Using charset: {}", charset);
|
||||
builder.setCharset(charset);
|
||||
|
||||
DiskFileItemFactory factory = builder.get();
|
||||
return new JakartaServletDiskFileUpload(factory);
|
||||
}
|
||||
|
||||
private String readStream(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
for (int length; (length = inputStream.read(buffer)) != -1; ) {
|
||||
result.write(buffer, 0, length);
|
||||
}
|
||||
return result.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the FileItemStream as a file field.
|
||||
* Processes the FileItem as a normal form field.
|
||||
*
|
||||
* @param itemStream file item stream
|
||||
* @param location location
|
||||
* @param fileItemInput a form field item input
|
||||
*/
|
||||
protected void processFileItemStreamAsFileField(FileItemInput itemStream, String location) {
|
||||
// Skip file uploads that don't have a file name - meaning that no file was selected.
|
||||
if (itemStream.getName() == null || itemStream.getName().trim().isEmpty()) {
|
||||
LOG.debug("No file has been uploaded for the field: {}", itemStream.getFieldName());
|
||||
protected void processFileItemAsFormField(FileItemInput fileItemInput) throws IOException {
|
||||
String fieldName = fileItemInput.getFieldName();
|
||||
String fieldValue = readStream(fileItemInput.getInputStream());
|
||||
|
||||
if (exceedsMaxStringLength(fieldName, fieldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = null;
|
||||
try {
|
||||
// Create the temporary upload file.
|
||||
file = createTemporaryFile(itemStream.getName(), location);
|
||||
List<String> values;
|
||||
if (parameters.containsKey(fieldName)) {
|
||||
values = parameters.get(fieldName);
|
||||
} else {
|
||||
values = new ArrayList<>();
|
||||
parameters.put(fieldName, values);
|
||||
}
|
||||
values.add(fieldValue);
|
||||
}
|
||||
|
||||
if (streamFileToDisk(itemStream, file)) {
|
||||
createFileInfoFromItemStream(itemStream, file);
|
||||
/**
|
||||
* @return actual size of already uploaded files
|
||||
*/
|
||||
protected Long actualSizeOfUploadedFiles() {
|
||||
return uploadedFiles.values().stream()
|
||||
.map(files -> files.stream().map(UploadedFile::length).reduce(0L, Long::sum))
|
||||
.reduce(0L, Long::sum);
|
||||
}
|
||||
|
||||
private boolean exceedsMaxFiles(FileItemInput fileItemInput) {
|
||||
if (maxFiles != null && maxFiles == uploadedFiles.size()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Cannot accept another file: {} as it will exceed max files: {}",
|
||||
sanitizeNewlines(fileItemInput.getName()), maxFiles);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (file != null) {
|
||||
try {
|
||||
file.delete();
|
||||
} catch (SecurityException se) {
|
||||
LOG.warn("Failed to delete '{}' due to security exception above.", file.getName(), se);
|
||||
}
|
||||
LocalizedMessage errorMessage = buildErrorMessage(
|
||||
FileUploadFileCountLimitException.class,
|
||||
String.format("File %s exceeds allowed maximum number of files %s",
|
||||
fileItemInput.getName(), maxFiles),
|
||||
new Object[]{maxFiles, uploadedFiles.size()}
|
||||
);
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void exceedsMaxSizeOfFiles(FileItemInput fileItemInput, File file, Long currentFilesSize) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("File: {} of size: {} exceeds allowed max size: {}, actual size of already uploaded files: {}",
|
||||
sanitizeNewlines(fileItemInput.getName()), file.length(), maxSizeOfFiles, currentFilesSize
|
||||
);
|
||||
}
|
||||
LocalizedMessage errorMessage = buildErrorMessage(
|
||||
FileUploadSizeException.class,
|
||||
String.format("Size %s of file %s exceeds allowed max size %s", file.length(),
|
||||
fileItemInput.getName(), maxSizeOfFiles),
|
||||
new Object[]{maxSizeOfFiles, currentFilesSize}
|
||||
);
|
||||
if (!errors.contains(errorMessage)) {
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
if (!file.delete() && LOG.isWarnEnabled()) {
|
||||
LOG.warn("Cannot delete file: {} which exceeds maximum size: {} of all files!",
|
||||
sanitizeNewlines(fileItemInput.getName()), maxSizeOfFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the FileItem as a file field.
|
||||
*
|
||||
* @param fileItemInput file item representing upload file
|
||||
* @param location location
|
||||
*/
|
||||
protected void processFileItemAsFileField(FileItemInput fileItemInput, Path location) throws IOException {
|
||||
// Skip file uploads that don't have a file name - meaning that no file was selected.
|
||||
if (fileItemInput.getName() == null || fileItemInput.getName().trim().isEmpty()) {
|
||||
LOG.debug(() -> "No file has been uploaded for the field: " + sanitizeNewlines(fileItemInput.getFieldName()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (exceedsMaxFiles(fileItemInput)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File file = createTemporaryFile(fileItemInput.getName(), location);
|
||||
streamFileToDisk(fileItemInput, file);
|
||||
|
||||
Long currentFilesSize = maxSizeOfFiles != null ? actualSizeOfUploadedFiles() : null;
|
||||
if (maxSizeOfFiles != null && currentFilesSize + file.length() >= maxSizeOfFiles) {
|
||||
exceedsMaxSizeOfFiles(fileItemInput, file, currentFilesSize);
|
||||
} else {
|
||||
createUploadedFile(fileItemInput, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,121 +211,54 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
|
||||
*
|
||||
* @param fileName file name
|
||||
* @param location location
|
||||
* @return temporary file based on the given filename and location
|
||||
* @throws IOException in case of IO errors
|
||||
* @return a temporary file based on the given filename and location
|
||||
*/
|
||||
protected File createTemporaryFile(String fileName, String location) throws IOException {
|
||||
String name = fileName
|
||||
.substring(fileName.lastIndexOf('/') + 1)
|
||||
.substring(fileName.lastIndexOf('\\') + 1);
|
||||
|
||||
String prefix = name;
|
||||
String suffix = "";
|
||||
|
||||
if (name.contains(".")) {
|
||||
prefix = name.substring(0, name.lastIndexOf('.'));
|
||||
suffix = name.substring(name.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
if (prefix.length() < 3) {
|
||||
prefix = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
File file = File.createTempFile(prefix + "_", suffix, new File(location));
|
||||
LOG.debug("Creating temporary file '{}' (originally '{}').", file.getName(), fileName);
|
||||
protected File createTemporaryFile(String fileName, Path location) {
|
||||
String uid = UUID.randomUUID().toString().replace("-", "_");
|
||||
File file = location.resolve("upload_" + uid + ".tmp").toFile();
|
||||
LOG.debug("Creating temporary file: {} (originally: {})", file.getName(), fileName);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the file upload stream to the specified file.
|
||||
*
|
||||
* @param itemStream file item stream
|
||||
* @param file the file
|
||||
* @return true if stream was successfully
|
||||
* @throws IOException in case of IO errors
|
||||
* @param fileItemInput file item input
|
||||
* @param file the file
|
||||
*/
|
||||
protected boolean streamFileToDisk(FileItemInput itemStream, File file) throws IOException {
|
||||
boolean result;
|
||||
try (InputStream input = itemStream.getInputStream();
|
||||
OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
|
||||
protected void streamFileToDisk(FileItemInput fileItemInput, File file) throws IOException {
|
||||
InputStream input = fileItemInput.getInputStream();
|
||||
try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
LOG.debug("Streaming file using buffer size {}.", bufferSize);
|
||||
LOG.debug("Streaming file: {} using buffer size: {}", fileItemInput.getName(), bufferSize);
|
||||
for (int length; ((length = input.read(buffer)) > 0); ) {
|
||||
output.write(buffer, 0, length);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an internal <code>FileInfo</code> structure used to pass information
|
||||
* to the <code>FileUploadInterceptor</code> during the interceptor stack
|
||||
* invocation process.
|
||||
* Create {@link UploadedFile} abstraction over uploaded file
|
||||
*
|
||||
* @param itemStream file item stream
|
||||
* @param file the file
|
||||
* @param fileItemInput file item stream
|
||||
* @param file the file
|
||||
*/
|
||||
protected void createFileInfoFromItemStream(FileItemInput itemStream, File file) {
|
||||
// gather attributes from file upload stream.
|
||||
String fileName = itemStream.getName();
|
||||
String fieldName = itemStream.getFieldName();
|
||||
// create internal structure
|
||||
FileInfo fileInfo = new FileInfo(file, itemStream.getContentType(), fileName);
|
||||
// append or create new entry.
|
||||
if (!fileInfos.containsKey(fieldName)) {
|
||||
List<FileInfo> infos = new ArrayList<>();
|
||||
infos.add(fileInfo);
|
||||
fileInfos.put(fieldName, infos);
|
||||
protected void createUploadedFile(FileItemInput fileItemInput, File file) {
|
||||
String fileName = fileItemInput.getName();
|
||||
String fieldName = fileItemInput.getFieldName();
|
||||
|
||||
UploadedFile uploadedFile = StrutsUploadedFile.Builder
|
||||
.create(file)
|
||||
.withOriginalName(fileName)
|
||||
.withContentType(fileItemInput.getContentType())
|
||||
.build();
|
||||
|
||||
if (uploadedFiles.containsKey(fieldName)) {
|
||||
uploadedFiles.get(fieldName).add(uploadedFile);
|
||||
} else {
|
||||
fileInfos.get(fieldName).add(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal data structure used to store a reference to information needed
|
||||
* to later pass post processing data to the <code>FileUploadInterceptor</code>.
|
||||
*
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public static class FileInfo implements Serializable {
|
||||
|
||||
private final File file;
|
||||
private final String contentType;
|
||||
private final String originalName;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @param file the file
|
||||
* @param contentType content type
|
||||
* @param originalName original file name
|
||||
*/
|
||||
public FileInfo(File file, String contentType, String originalName) {
|
||||
this.file = file;
|
||||
this.contentType = contentType;
|
||||
this.originalName = originalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the file
|
||||
*/
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return content type
|
||||
*/
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return original file name
|
||||
*/
|
||||
public String getOriginalName() {
|
||||
return originalName;
|
||||
List<UploadedFile> infos = new ArrayList<>();
|
||||
infos.add(uploadedFile);
|
||||
uploadedFiles.put(fieldName, infos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
public interface MultiPartRequest {
|
||||
|
||||
void parse(HttpServletRequest request, String saveDir) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns an enumeration of the parameter names for uploaded files
|
||||
*
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
import org.apache.struts2.dispatcher.StrutsRequestWrapper;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ public class StrutsUploadedFile implements UploadedFile {
|
||||
|
||||
/**
|
||||
* Use builder instead of constructor
|
||||
*
|
||||
* @param file an uploaded file
|
||||
* @deprecated since Struts 6.4.0
|
||||
*/
|
||||
@@ -87,9 +88,9 @@ public class StrutsUploadedFile implements UploadedFile {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StrutsUploadedFile{" +
|
||||
"contentType='" + contentType + '\'' +
|
||||
", originalName='" + originalName + '\'' +
|
||||
'}';
|
||||
"contentType='" + contentType + '\'' +
|
||||
", originalName='" + originalName + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
@@ -21,24 +21,48 @@ package org.apache.struts2.dispatcher.multipart;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Virtual representation of a uploaded file used by {@link MultiPartRequest}
|
||||
* Virtual representation of an uploaded file used by {@link MultiPartRequest}
|
||||
*/
|
||||
public interface UploadedFile extends Serializable {
|
||||
|
||||
/**
|
||||
* @return size of the content of file/stream/array
|
||||
*/
|
||||
Long length();
|
||||
|
||||
/**
|
||||
* @return a local name of the file
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @return original file name from upload source
|
||||
*/
|
||||
String getOriginalName();
|
||||
|
||||
/**
|
||||
* @return indicates if this is a real file or maybe just in-memory stream
|
||||
*/
|
||||
boolean isFile();
|
||||
|
||||
/**
|
||||
* @return removes a local copy of the uploaded file/stream
|
||||
*/
|
||||
boolean delete();
|
||||
|
||||
/**
|
||||
* @return an absolute path of the file if possible
|
||||
*/
|
||||
String getAbsolutePath();
|
||||
|
||||
/**
|
||||
* @return content of the upload file
|
||||
*/
|
||||
Object getContent();
|
||||
|
||||
/**
|
||||
* @return content type of the uploaded file
|
||||
*/
|
||||
String getContentType();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Deprecated
|
||||
public interface ApplicationAware extends org.apache.struts2.action.ApplicationAware {
|
||||
|
||||
void setApplication(Map<String, Object> application);
|
||||
|
||||
@Override
|
||||
default void withApplication(Map<String, Object> application) {
|
||||
setApplication(application);
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,14 @@ import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.ActionProxy;
|
||||
import com.opensymphony.xwork2.interceptor.ValidationAware;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.Parameter;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
@@ -192,17 +192,26 @@ public class FileUploadInterceptor extends AbstractFileUploadInterceptor {
|
||||
|
||||
HttpServletRequest request = ac.getServletRequest();
|
||||
|
||||
if (!(request instanceof MultiPartRequestWrapper)) {
|
||||
if (!(request instanceof MultiPartRequestWrapper multiWrapper)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
ActionProxy proxy = invocation.getProxy();
|
||||
LOG.debug(getTextMessage(STRUTS_MESSAGES_BYPASS_REQUEST_KEY, new String[]{proxy.getNamespace(), proxy.getActionName()}));
|
||||
LOG.debug(getTextMessage(STRUTS_MESSAGES_BYPASS_REQUEST_KEY,
|
||||
new String[]{proxy.getNamespace(), proxy.getActionName()})
|
||||
);
|
||||
}
|
||||
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
Object action = invocation.getAction();
|
||||
MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
|
||||
if (action instanceof UploadedFilesAware) {
|
||||
LOG.debug("Ignoring action: {} implementing: {} as it will be handled by: {}",
|
||||
invocation.getProxy().getActionName(),
|
||||
UploadedFilesAware.class.getSimpleName(),
|
||||
ActionFileUploadInterceptor.class.getSimpleName()
|
||||
);
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
applyValidation(action, multiWrapper);
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
|
||||
@Deprecated
|
||||
public interface HttpParametersAware extends org.apache.struts2.action.ParametersAware {
|
||||
|
||||
void setParameters(HttpParameters parameters);
|
||||
|
||||
@Override
|
||||
default void withParameters(HttpParameters parameters) {
|
||||
setParameters(parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
@Deprecated
|
||||
public interface ParameterAware extends org.apache.struts2.action.ParametersAware {
|
||||
|
||||
void setParameters(Map<String, String[]> map);
|
||||
|
||||
@Override
|
||||
default void withParameters(HttpParameters parameters) {
|
||||
setParameters(parameters.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> e.getValue().getMultipleValues())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@Deprecated
|
||||
public interface PrincipalAware extends org.apache.struts2.action.PrincipalAware {
|
||||
|
||||
void setPrincipalProxy(PrincipalProxy principalProxy);
|
||||
|
||||
@Override
|
||||
default void withPrincipalProxy(PrincipalProxy principalProxy) {
|
||||
setPrincipalProxy(principalProxy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.struts2.dispatcher.RequestMap;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Map;
|
||||
|
||||
@Deprecated
|
||||
public interface RequestAware extends ServletRequestAware {
|
||||
|
||||
@Override
|
||||
default void setServletRequest(HttpServletRequest httpServletRequest) {
|
||||
// default no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
default void withServletRequest(HttpServletRequest request) {
|
||||
ServletRequestAware.super.withServletRequest(request);
|
||||
setRequest(new RequestMap(request));
|
||||
}
|
||||
|
||||
void setRequest(Map<String, Object> request);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Deprecated
|
||||
public interface ServletRequestAware extends org.apache.struts2.action.ServletRequestAware {
|
||||
|
||||
void setServletRequest(HttpServletRequest httpServletRequest);
|
||||
|
||||
@Override
|
||||
default void withServletRequest(HttpServletRequest request) {
|
||||
setServletRequest(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@Deprecated
|
||||
public interface ServletResponseAware extends org.apache.struts2.action.ServletResponseAware {
|
||||
|
||||
void setServletResponse(HttpServletResponse httpServletResponse);
|
||||
|
||||
@Override
|
||||
default void withServletResponse(HttpServletResponse response) {
|
||||
setServletResponse(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Deprecated
|
||||
public interface SessionAware extends org.apache.struts2.action.SessionAware {
|
||||
|
||||
void setSession(Map<String, Object> session);
|
||||
|
||||
@Override
|
||||
default void withSession(Map<String, Object> session) {
|
||||
setSession(session);
|
||||
}
|
||||
}
|
||||
+201
-1
@@ -25,6 +25,7 @@ import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
|
||||
import com.opensymphony.xwork2.interceptor.ValidationAware;
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
import com.opensymphony.xwork2.util.ClearableValueStack;
|
||||
import com.opensymphony.xwork2.util.MemberAccessValueStack;
|
||||
@@ -33,6 +34,7 @@ import com.opensymphony.xwork2.util.ValueStack;
|
||||
import com.opensymphony.xwork2.util.ValueStackFactory;
|
||||
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.StrutsConstants;
|
||||
@@ -41,17 +43,33 @@ import org.apache.struts2.action.ParameterNameAware;
|
||||
import org.apache.struts2.action.ParameterValueAware;
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
import org.apache.struts2.dispatcher.Parameter;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS;
|
||||
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS_STR;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.apache.commons.lang3.StringUtils.indexOfAny;
|
||||
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
|
||||
|
||||
/**
|
||||
@@ -70,8 +88,11 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
private boolean dmiEnabled = false;
|
||||
|
||||
protected boolean ordered = false;
|
||||
protected boolean requireAnnotations = false;
|
||||
protected boolean requireAnnotationsTransitionMode = false;
|
||||
|
||||
private ValueStackFactory valueStackFactory;
|
||||
protected ThreadAllowlist threadAllowlist;
|
||||
private ExcludedPatternsChecker excludedPatterns;
|
||||
private AcceptedPatternsChecker acceptedPatterns;
|
||||
private Set<Pattern> excludedValuePatterns = null;
|
||||
@@ -82,11 +103,35 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
this.valueStackFactory = valueStackFactory;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
|
||||
this.threadAllowlist = threadAllowlist;
|
||||
}
|
||||
|
||||
@Inject(StrutsConstants.STRUTS_DEVMODE)
|
||||
public void setDevMode(String mode) {
|
||||
this.devMode = BooleanUtils.toBoolean(mode);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS, required = false)
|
||||
public void setRequireAnnotations(String requireAnnotations) {
|
||||
this.requireAnnotations = BooleanUtils.toBoolean(requireAnnotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* When 'Transition Mode' is enabled, parameters that are not 'nested' will be accepted without annotations. What
|
||||
* this means in practice is that all public setters on an Action will be exposed for parameter injection again, and
|
||||
* only 'nested' parameters, i.e. public getters on an Action, will require annotations.
|
||||
* <p>
|
||||
* In this mode, the OGNL auto-allowlisting capability is not degraded in any way, and as such, it offers a
|
||||
* convenient option for applications to enable the OGNL allowlist capability whilst they work through the process
|
||||
* of annotating all their Action parameters.
|
||||
*/
|
||||
@Inject(value = StrutsConstants.STRUTS_PARAMETERS_REQUIRE_ANNOTATIONS_TRANSITION, required = false)
|
||||
public void setRequireAnnotationsTransitionMode(String transitionMode) {
|
||||
this.requireAnnotationsTransitionMode = BooleanUtils.toBoolean(transitionMode);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public void setExcludedPatterns(ExcludedPatternsChecker excludedPatterns) {
|
||||
this.excludedPatterns = excludedPatterns;
|
||||
@@ -295,13 +340,168 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
* @return true if parameter is accepted
|
||||
*/
|
||||
protected boolean isAcceptableParameter(String name, Object action) {
|
||||
return acceptableName(name) && isAcceptableParameterNameAware(name, action);
|
||||
return acceptableName(name) && isAcceptableParameterNameAware(name, action) && isParameterAnnotatedAndAllowlist(name, action);
|
||||
}
|
||||
|
||||
protected boolean isAcceptableParameterNameAware(String name, Object action) {
|
||||
return !(action instanceof ParameterNameAware) || ((ParameterNameAware) action).acceptableParameterName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Action class member corresponding to a parameter is appropriately annotated with
|
||||
* {@link StrutsParameter} and OGNL allowlists any necessary classes.
|
||||
* <p>
|
||||
* Note that this logic relies on the use of {@link DefaultAcceptedPatternsChecker#NESTING_CHARS} and may also
|
||||
* be adversely impacted by the use of custom OGNL property accessors.
|
||||
*/
|
||||
protected boolean isParameterAnnotatedAndAllowlist(String name, Object action) {
|
||||
if (!requireAnnotations) {
|
||||
return true;
|
||||
}
|
||||
|
||||
long paramDepth = name.codePoints().mapToObj(c -> (char) c).filter(NESTING_CHARS::contains).count();
|
||||
if (requireAnnotationsTransitionMode && paramDepth == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int nestingIndex = indexOfAny(name, NESTING_CHARS_STR);
|
||||
String rootProperty = nestingIndex == -1 ? name : name.substring(0, nestingIndex);
|
||||
String normalisedRootProperty = Character.toLowerCase(rootProperty.charAt(0)) + rootProperty.substring(1);
|
||||
|
||||
return hasValidAnnotatedMember(normalisedRootProperty, action, paramDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that we check for a public field last or only if there is no valid, annotated property descriptor. This is
|
||||
* because this check is likely to fail more often than not, as the relative use of public fields is low - so we
|
||||
* save computation by checking this last.
|
||||
*/
|
||||
protected boolean hasValidAnnotatedMember(String rootProperty, Object action, long paramDepth) {
|
||||
BeanInfo beanInfo = getBeanInfo(action);
|
||||
if (beanInfo == null) {
|
||||
return hasValidAnnotatedField(action, rootProperty, paramDepth);
|
||||
}
|
||||
|
||||
Optional<PropertyDescriptor> propDescOpt = Arrays.stream(beanInfo.getPropertyDescriptors())
|
||||
.filter(desc -> desc.getName().equals(rootProperty)).findFirst();
|
||||
if (!propDescOpt.isPresent()) {
|
||||
return hasValidAnnotatedField(action, rootProperty, paramDepth);
|
||||
}
|
||||
|
||||
if (hasValidAnnotatedPropertyDescriptor(propDescOpt.get(), paramDepth)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasValidAnnotatedField(action, rootProperty, paramDepth);
|
||||
}
|
||||
|
||||
protected boolean hasValidAnnotatedPropertyDescriptor(PropertyDescriptor propDesc, long paramDepth) {
|
||||
Method relevantMethod = paramDepth == 0 ? propDesc.getWriteMethod() : propDesc.getReadMethod();
|
||||
if (relevantMethod == null) {
|
||||
return false;
|
||||
}
|
||||
if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
|
||||
LOG.debug(
|
||||
"Parameter injection for method [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
relevantMethod.getName(),
|
||||
relevantMethod.getDeclaringClass().getName());
|
||||
return false;
|
||||
}
|
||||
if (paramDepth >= 1) {
|
||||
allowlistClass(relevantMethod.getReturnType());
|
||||
}
|
||||
if (paramDepth >= 2) {
|
||||
allowlistReturnTypeIfParameterized(relevantMethod);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void allowlistReturnTypeIfParameterized(Method method) {
|
||||
allowlistParameterizedTypeArg(method.getGenericReturnType());
|
||||
}
|
||||
|
||||
protected void allowlistParameterizedTypeArg(Type genericType) {
|
||||
if (!(genericType instanceof ParameterizedType)) {
|
||||
return;
|
||||
}
|
||||
Type[] paramTypes = ((ParameterizedType) genericType).getActualTypeArguments();
|
||||
allowlistParamType(paramTypes[0]);
|
||||
if (paramTypes.length > 1) {
|
||||
// Probably useful for Map or Map-like classes
|
||||
allowlistParamType(paramTypes[1]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void allowlistParamType(Type paramType) {
|
||||
if (paramType instanceof Class) {
|
||||
allowlistClass((Class<?>) paramType);
|
||||
}
|
||||
}
|
||||
|
||||
protected void allowlistClass(Class<?> clazz) {
|
||||
threadAllowlist.allowClass(clazz);
|
||||
ClassUtils.getAllSuperclasses(clazz).forEach(threadAllowlist::allowClass);
|
||||
ClassUtils.getAllInterfaces(clazz).forEach(threadAllowlist::allowClass);
|
||||
}
|
||||
|
||||
protected boolean hasValidAnnotatedField(Object action, String fieldName, long paramDepth) {
|
||||
Field field;
|
||||
try {
|
||||
field = action.getClass().getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
return false;
|
||||
}
|
||||
if (!Modifier.isPublic(field.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
if (getPermittedInjectionDepth(field) < paramDepth) {
|
||||
LOG.debug(
|
||||
"Parameter injection for field [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
fieldName,
|
||||
action.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
if (paramDepth >= 1) {
|
||||
allowlistClass(field.getType());
|
||||
}
|
||||
if (paramDepth >= 2) {
|
||||
allowlistFieldIfParameterized(field);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void allowlistFieldIfParameterized(Field field) {
|
||||
allowlistParameterizedTypeArg(field.getGenericType());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return permitted injection depth where -1 indicates not permitted
|
||||
*/
|
||||
protected int getPermittedInjectionDepth(AnnotatedElement element) {
|
||||
StrutsParameter annotation = getParameterAnnotation(element);
|
||||
if (annotation == null) {
|
||||
return -1;
|
||||
}
|
||||
return annotation.depth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation retrieval logic. Can be overridden to support extending annotations or some other form of annotation
|
||||
* inheritance.
|
||||
*/
|
||||
protected StrutsParameter getParameterAnnotation(AnnotatedElement element) {
|
||||
return element.getAnnotation(StrutsParameter.class);
|
||||
}
|
||||
|
||||
protected BeanInfo getBeanInfo(Object action) {
|
||||
try {
|
||||
return Introspector.getBeanInfo(action.getClass());
|
||||
} catch (IntrospectionException e) {
|
||||
LOG.warn("Error introspecting Action {} for parameter injection validation", action.getClass(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if parameter value can be accepted or thrown away
|
||||
*
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.parameter;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to annotate public getter/setter methods or fields on {@link com.opensymphony.xwork2.Action} classes that are
|
||||
* intended for parameter injection by the {@link ParametersInterceptor}.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface StrutsParameter {
|
||||
|
||||
/**
|
||||
* The depth to which parameter injection is permitted, where a depth of 0 only allows setters/fields directly on
|
||||
* the action class. Setting within a POJO on an action will require a depth of 1 or more depending on the level of
|
||||
* nesting within the POJO.
|
||||
* <p>
|
||||
* In a practical sense, the depth dictates the number of periods or brackets that can appear in the parameter name.
|
||||
*/
|
||||
int depth() default 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.ognl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
|
||||
/**
|
||||
* Allows any bean to allowlist a class for use in OGNL expressions, for the current thread only. The allowlist can be
|
||||
* cleared once any desired OGNL expressions have been evaluated.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*/
|
||||
public class ThreadAllowlist {
|
||||
|
||||
private final ThreadLocal<Set<Class<?>>> allowlist = new ThreadLocal<>();
|
||||
|
||||
public void allowClass(Class<?> clazz) {
|
||||
if (allowlist.get() == null) {
|
||||
allowlist.set(new HashSet<>());
|
||||
}
|
||||
allowlist.get().add(clazz);
|
||||
}
|
||||
|
||||
public void clearAllowlist() {
|
||||
allowlist.remove();
|
||||
}
|
||||
|
||||
public Set<Class<?>> getAllowlist() {
|
||||
return allowlist.get() != null ? unmodifiableSet(allowlist.get()) : emptySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.struts2.util;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
||||
@Deprecated
|
||||
public interface ServletContextAware extends org.apache.struts2.action.ServletContextAware {
|
||||
|
||||
void setServletContext(ServletContext context);
|
||||
|
||||
@Override
|
||||
default void withServletContext(ServletContext context) {
|
||||
setServletContext(context);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.jsp.JspException;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import org.apache.struts2.components.Component;
|
||||
|
||||
import com.opensymphony.xwork2.inject.Container;
|
||||
@@ -39,8 +38,7 @@ public abstract class ComponentTagSupport extends StrutsBodyTagSupport {
|
||||
public int doEndTag() throws JspException {
|
||||
component.end(pageContext.getOut(), getBody());
|
||||
component = null; // Always clear component reference (since clearTagStateForTagPoolingServers() is conditional).
|
||||
clearTagStateForTagPoolingServers();
|
||||
return EVAL_PAGE;
|
||||
return super.doEndTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,7 +47,7 @@ public abstract class ComponentTagSupport extends StrutsBodyTagSupport {
|
||||
component = getBean(stack, (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse());
|
||||
Container container = stack.getActionContext().getContainer();
|
||||
container.inject(component);
|
||||
|
||||
|
||||
populateParams();
|
||||
boolean evalBody = component.start(pageContext.getOut());
|
||||
|
||||
@@ -62,7 +60,7 @@ public abstract class ComponentTagSupport extends StrutsBodyTagSupport {
|
||||
|
||||
/**
|
||||
* Define method to populate component state based on the Tag parameters.
|
||||
*
|
||||
*
|
||||
* Descendants should override this method for custom behaviour, but should <em>always</em> call the ancestor method when doing so.
|
||||
*/
|
||||
protected void populateParams() {
|
||||
@@ -71,7 +69,7 @@ public abstract class ComponentTagSupport extends StrutsBodyTagSupport {
|
||||
|
||||
/**
|
||||
* Specialized method to populate the performClearTagStateForTagPoolingServers state of the Component to match the value set in the Tag.
|
||||
*
|
||||
*
|
||||
* Generally only unit tests would call this method directly, to avoid calling the whole populateParams() chain again after doStartTag()
|
||||
* has been called. Doing that can break tag / component state behaviour, but unit tests still need a way to set the
|
||||
* performClearTagStateForTagPoolingServers state for the component (which only comes into being after doStartTag() is called).
|
||||
|
||||
@@ -26,23 +26,29 @@ package org.apache.struts2.views.jsp;
|
||||
* <li>count: iterations so far, starts on 1. count is always index + 1</li>
|
||||
* <li>first: true if index == 0</li>
|
||||
* <li>even: true if (index + 1) % 2 == 0</li>
|
||||
* <li>last: true if current iteration is the last iteration</li>
|
||||
* <li>last: true if current iteration is the last iteration</li>
|
||||
* <li>odd: true if (index + 1) % 2 == 1</li>
|
||||
* </ul>
|
||||
* <p>Example</p>
|
||||
* <pre>
|
||||
* <s:iterator status="status" value='{0, 1}'>
|
||||
* Index: <s:property value="%{#status.index}" /> <br />
|
||||
* Count: <s:property value="%{#status.count}" /> <br />
|
||||
* Index Str: <s:property value="%{#status.indexStr}" /> <br />
|
||||
* Count: <s:property value="%{#status.count}" /> <br />
|
||||
* Count Str: <s:property value="%{#status.countStr}" /> <br />
|
||||
* </s:iterator>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <p>will print</p>
|
||||
* <pre>
|
||||
* Index: 0
|
||||
* Index Str: 0
|
||||
* Count: 1
|
||||
* Count Str: 1
|
||||
* Index: 1
|
||||
* Index Str: 1
|
||||
* Count: 2
|
||||
* Count Str: 2
|
||||
* </pre>
|
||||
*/
|
||||
public class IteratorStatus {
|
||||
@@ -56,6 +62,10 @@ public class IteratorStatus {
|
||||
return state.index + 1;
|
||||
}
|
||||
|
||||
public String getCountStr() {
|
||||
return String.valueOf(state.index + 1);
|
||||
}
|
||||
|
||||
public boolean isEven() {
|
||||
return ((state.index + 1) % 2) == 0;
|
||||
}
|
||||
@@ -68,6 +78,10 @@ public class IteratorStatus {
|
||||
return state.index;
|
||||
}
|
||||
|
||||
public String getIndexStr() {
|
||||
return String.valueOf(state.index);
|
||||
}
|
||||
|
||||
public boolean isLast() {
|
||||
return state.last;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ struts.messages.upload.error.FileUploadSizeException=Request exceeded allowed si
|
||||
# 0 - content type
|
||||
struts.messages.upload.error.FileUploadContentTypeException=Request has wrong content type: {0}!
|
||||
|
||||
struts.messages.upload.error.FileUploadException=Error uploading: {0}!
|
||||
# Default error message when handling multi-part request
|
||||
struts.messages.upload.error.FileUploadException=Error parsing the multi-part request.
|
||||
|
||||
devmode.notification=Developer Notification (set struts.devMode to false to disable this message):\n{0}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@
|
||||
<bean type="org.apache.struts2.ognl.OgnlGuard" name="struts"
|
||||
class="org.apache.struts2.ognl.StrutsOgnlGuard"/>
|
||||
<bean class="org.apache.struts2.ognl.ProviderAllowlist"/>
|
||||
<bean class="org.apache.struts2.ognl.ThreadAllowlist"/>
|
||||
|
||||
<bean type="com.opensymphony.xwork2.util.TextParser" name="struts"
|
||||
class="com.opensymphony.xwork2.util.OgnlTextParser" scope="singleton"/>
|
||||
|
||||
@@ -371,6 +371,6 @@
|
||||
<td class="tag-attribute"></td>
|
||||
<td class="tag-attribute">false</td>
|
||||
<td class="tag-attribute">String</td>
|
||||
<td class="tag-attribute">Preset the value of input element.</td>
|
||||
<td class="tag-attribute">Ignored during file upload</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.opensymphony.xwork2.util.Foo;
|
||||
import ognl.MemberAccess;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.apache.struts2.ognl.ProviderAllowlist;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -54,18 +55,21 @@ public class SecurityMemberAccessTest {
|
||||
private FooBar target;
|
||||
protected SecurityMemberAccess sma;
|
||||
private ProviderAllowlist mockedProviderAllowlist;
|
||||
private ThreadAllowlist mockedThreadAllowlist;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
context = new HashMap<>();
|
||||
target = new FooBar();
|
||||
mockedProviderAllowlist = mock(ProviderAllowlist.class);
|
||||
mockedThreadAllowlist = mock(ThreadAllowlist.class);
|
||||
assignNewSma(true);
|
||||
}
|
||||
|
||||
protected void assignNewSma(boolean allowStaticFieldAccess) {
|
||||
when(mockedProviderAllowlist.getProviderAllowlist()).thenReturn(new HashSet<>());
|
||||
sma = new SecurityMemberAccess(mockedProviderAllowlist);
|
||||
when(mockedThreadAllowlist.getAllowlist()).thenReturn(new HashSet<>());
|
||||
sma = new SecurityMemberAccess(mockedProviderAllowlist, mockedThreadAllowlist);
|
||||
sma.useAllowStaticFieldAccess(String.valueOf(allowStaticFieldAccess));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ public class User implements UserMarker {
|
||||
private String email2;
|
||||
private String name;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setCollection(Collection collection) {
|
||||
this.collection = collection;
|
||||
|
||||
@@ -45,6 +45,7 @@ public class TestAction extends ActionSupport {
|
||||
private String result;
|
||||
private User user;
|
||||
private String[] array;
|
||||
private Object[] objectArray;
|
||||
private String[][] list;
|
||||
private List list2;
|
||||
private List list3;
|
||||
@@ -135,6 +136,14 @@ public class TestAction extends ActionSupport {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public Object[] getObjectArray() {
|
||||
return objectArray;
|
||||
}
|
||||
|
||||
public void setObjectArray(Object[] arrayObject) {
|
||||
this.objectArray = arrayObject;
|
||||
}
|
||||
|
||||
public String[][] getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -19,23 +19,38 @@
|
||||
package org.apache.struts2.components;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.test.User;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.apache.struts2.TestAction;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class IteratorComponentTest extends StrutsInternalTestCase {
|
||||
|
||||
private ValueStack stack;
|
||||
private IteratorComponent ic;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
stack = ActionContext.getContext().getValueStack();
|
||||
ic = new IteratorComponent(stack);
|
||||
ThreadAllowlist threadAllowlist = new ThreadAllowlist();
|
||||
ic.setThreadAllowlist(threadAllowlist);
|
||||
}
|
||||
|
||||
public void testIterator() throws Exception {
|
||||
// given
|
||||
final ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
stack.push(new FooAction());
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
IteratorComponent ic = new IteratorComponent(stack);
|
||||
ic.setValue("items");
|
||||
ic.setVar("val");
|
||||
|
||||
@@ -62,14 +77,53 @@ public class IteratorComponentTest extends StrutsInternalTestCase {
|
||||
assertEquals("item1 item2 item3 item4 ", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testIteratorWithBegin() throws Exception {
|
||||
public void testSimpleIterator() {
|
||||
// given
|
||||
stack.push(new FooAction());
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
ic.setBegin("1");
|
||||
ic.setEnd("8");
|
||||
ic.setStep("2");
|
||||
ic.setStatus("status");
|
||||
|
||||
Property prop = new Property(stack);
|
||||
Property status = new Property(stack);
|
||||
status.setValue("#status.index");
|
||||
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
|
||||
String body = " ";
|
||||
|
||||
// when
|
||||
assertTrue(ic.start(out));
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
status.start(out);
|
||||
status.end(out, body);
|
||||
prop.start(out);
|
||||
prop.end(out, body);
|
||||
ic.end(out, null);
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals("0 1 1 3 2 5 3 7 ", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testIteratorWithBegin() {
|
||||
// given
|
||||
final ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
stack.push(new FooAction());
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
IteratorComponent ic = new IteratorComponent(stack);
|
||||
ic.setValue("items");
|
||||
ic.setVar("val");
|
||||
ic.setBegin("1");
|
||||
@@ -94,20 +148,18 @@ public class IteratorComponentTest extends StrutsInternalTestCase {
|
||||
assertEquals("item2 item3 item4 ", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testIteratorWithNulls() throws Exception {
|
||||
public void testIteratorWithNulls() {
|
||||
// given
|
||||
final ValueStack stack = ActionContext.getContext().getValueStack();
|
||||
stack.push(new FooAction() {
|
||||
private List items = Arrays.asList("1", "2", null, "4");
|
||||
private final List<String> items = Arrays.asList("1", "2", null, "4");
|
||||
|
||||
public List getItems() {
|
||||
public List<String> getItems() {
|
||||
return items;
|
||||
}
|
||||
});
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
IteratorComponent ic = new IteratorComponent(stack);
|
||||
ic.setValue("items");
|
||||
ic.setVar("val");
|
||||
Property prop = new Property(stack);
|
||||
@@ -132,15 +184,147 @@ public class IteratorComponentTest extends StrutsInternalTestCase {
|
||||
assertEquals("1, 2, , 4, ", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testIteratorWithDifferentLocale() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(new Locale("fa_IR"));
|
||||
stack.push(new FooAction());
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
ic.setBegin("1");
|
||||
ic.setEnd("3");
|
||||
ic.setStatus("status");
|
||||
|
||||
Property prop = new Property(stack);
|
||||
Property status = new Property(stack);
|
||||
status.setValue("#status.count");
|
||||
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
|
||||
String body = ",";
|
||||
|
||||
// when
|
||||
assertTrue(ic.start(out));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
status.start(out);
|
||||
status.end(out, body);
|
||||
|
||||
prop.start(out);
|
||||
prop.end(out, body);
|
||||
ic.end(out, null);
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals("1,1,2,2,3,3,", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testListOfBeansIterator() {
|
||||
// given
|
||||
TestAction action = new TestAction();
|
||||
action.setList2(new ArrayList<User>() {{
|
||||
add(new User("Anton"));
|
||||
add(new User("Tym"));
|
||||
add(new User("Luk"));
|
||||
}});
|
||||
stack.push(action);
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
ic.setValue("list2");
|
||||
ic.setStatus("status");
|
||||
|
||||
Property prop = new Property(stack);
|
||||
prop.setValue("name");
|
||||
Property status = new Property(stack);
|
||||
status.setValue("#status.indexStr");
|
||||
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
|
||||
String body = ",";
|
||||
|
||||
// when
|
||||
assertTrue(ic.start(out));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
status.start(out);
|
||||
status.end(out, body);
|
||||
|
||||
prop.start(out);
|
||||
prop.end(out, body);
|
||||
|
||||
ic.end(out, null);
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals("0,Anton,1,Tym,2,Luk,", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
public void testArrayOfBeansIterator() {
|
||||
// given
|
||||
TestAction action = new TestAction();
|
||||
action.setObjectArray(new ArrayList<User>() {{
|
||||
add(new User("Anton"));
|
||||
add(new User("Tym"));
|
||||
add(new User("Luk"));
|
||||
}}.toArray());
|
||||
stack.push(action);
|
||||
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
ic.setValue("objectArray");
|
||||
ic.setStatus("status");
|
||||
|
||||
Property prop = new Property(stack);
|
||||
prop.setValue("name");
|
||||
Property status = new Property(stack);
|
||||
status.setValue("#status.countStr");
|
||||
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
ic.getComponentStack().push(status);
|
||||
ic.getComponentStack().push(prop);
|
||||
|
||||
String body = " ";
|
||||
|
||||
// when
|
||||
assertTrue(ic.start(out));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
status.start(out);
|
||||
status.end(out, body);
|
||||
|
||||
prop.start(out);
|
||||
prop.end(out, body);
|
||||
|
||||
ic.end(out, null);
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals("1 Anton 2 Tym 3 Luk ", out.getBuffer().toString());
|
||||
}
|
||||
|
||||
static class FooAction {
|
||||
|
||||
private List items;
|
||||
private final List<String> items;
|
||||
|
||||
public FooAction() {
|
||||
items = Arrays.asList("item1", "item2", "item3", "item4");
|
||||
}
|
||||
|
||||
public List getItems() {
|
||||
public List<String> getItems() {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ public class SettingsTest extends StrutsInternalTestCase {
|
||||
public void testSettings() {
|
||||
Settings settings = new DefaultSettings();
|
||||
|
||||
assertEquals("12345", settings.get(StrutsConstants.STRUTS_MULTIPART_MAXSIZE));
|
||||
assertEquals("\\temp", settings.get(StrutsConstants.STRUTS_MULTIPART_SAVEDIR));
|
||||
assertEquals("12345", settings.get(StrutsConstants.STRUTS_MULTIPART_MAX_SIZE));
|
||||
assertEquals("\\temp", settings.get(StrutsConstants.STRUTS_MULTIPART_SAVE_DIR));
|
||||
|
||||
assertEquals("test,org/apache/struts2/othertest", settings.get( StrutsConstants.STRUTS_CUSTOM_PROPERTIES));
|
||||
assertEquals("testvalue", settings.get("testkey"));
|
||||
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* 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.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
abstract class AbstractMultiPartRequestTest {
|
||||
|
||||
protected static String tempDir;
|
||||
|
||||
protected MockHttpServletRequest mockRequest;
|
||||
|
||||
protected final String boundary = "_boundary_";
|
||||
protected final String endline = "\r\n";
|
||||
|
||||
protected AbstractMultiPartRequest multiPart;
|
||||
|
||||
abstract protected AbstractMultiPartRequest createMultipartRequest();
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws IOException {
|
||||
File tempFile = File.createTempFile("struts", "fileupload");
|
||||
assertThat(tempFile.delete()).isTrue();
|
||||
assertThat(tempFile.mkdirs()).isTrue();
|
||||
tempDir = tempFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
mockRequest = new MockHttpServletRequest();
|
||||
mockRequest.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
mockRequest.setMethod("post");
|
||||
mockRequest.setContentType("multipart/form-data; boundary=" + boundary);
|
||||
|
||||
multiPart = createMultipartRequest();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
multiPart.cleanUp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uploadedFilesToDisk() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.setBufferSize("1"); // always write files into disk
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.containsOnly("file1", "file2");
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("1,2,3,4");
|
||||
});
|
||||
assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test2.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("5,6,7,8");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uploadedMultipleFilesToDisk() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file1", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.setBufferSize("1"); // always write files into disk
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.containsOnly("file1");
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
if (Objects.equals(file.getName(), "test1.csv")) {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("1,2,3,4");
|
||||
}
|
||||
if (Objects.equals(file.getName(), "test2.csv")) {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test2.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("5,6,7,8");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uploadedFilesWithLargeBuffer() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.setBufferSize("8192"); // streams files into disk using larger buffer
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.containsOnly("file1", "file2");
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("1,2,3,4");
|
||||
});
|
||||
assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test2.csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("5,6,7,8");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cleanUp() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.containsOnly("file1", "file2");
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("1,2,3,4");
|
||||
});
|
||||
assertThat(multiPart.getFile("file2")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test2.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("5,6,7,8");
|
||||
});
|
||||
|
||||
List<UploadedFile> uploadedFiles = new ArrayList<>();
|
||||
for (Map.Entry<String, List<UploadedFile>> entry : multiPart.uploadedFiles.entrySet()) {
|
||||
uploadedFiles.addAll(entry.getValue());
|
||||
}
|
||||
|
||||
// when
|
||||
multiPart.cleanUp();
|
||||
|
||||
// then
|
||||
assertThat(multiPart.uploadedFiles)
|
||||
.isEmpty();
|
||||
assertThat(multiPart.parameters)
|
||||
.isEmpty();
|
||||
assertThat(uploadedFiles).allSatisfy(file ->
|
||||
assertThat(file.getContent()).asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.doesNotExist()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonMultiPartUpload() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// given
|
||||
mockRequest.setContentType("");
|
||||
|
||||
// when
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadContentTypeException");
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxSize() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.setMaxSize("1");
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.uploadedFiles)
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadSizeException");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxFilesSize() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
multiPart.setMaxFileSize("1");
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadByteCountLimitException");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxFiles() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
multiPart.setMaxFiles("1");
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
assertThat(multiPart.errors)
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadFileCountLimitException");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxStringLength() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
formField("longText", "very long text") +
|
||||
formField("shortText", "short text") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
multiPart.setMaxStringLength("10");
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.parameter.too.long");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mismatchCharset() throws IOException {
|
||||
// give
|
||||
String content = formFile("file1", "test1.csv", "Ł,Ś,Ż,Ó") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
mockRequest.setCharacterEncoding(null);
|
||||
multiPart.setDefaultEncoding(StandardCharsets.ISO_8859_1.name());
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
// then
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
|
||||
.asList()
|
||||
.containsOnly("file1");
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("Ł,Ś,Ż,Ó");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void normalFields() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
formField("longText", "very long text") +
|
||||
formField("shortText", "short text") +
|
||||
formField("multi", "multi1") +
|
||||
formField("multi", "multi2") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
assertThat(multiPart.getErrors())
|
||||
.isEmpty();
|
||||
|
||||
assertThat(multiPart.getParameterNames().asIterator()).toIterable()
|
||||
.containsOnly("longText", "shortText", "multi");
|
||||
assertThat(multiPart.getParameterValues("longText"))
|
||||
.contains("very long text");
|
||||
assertThat(multiPart.getParameterValues("shortText"))
|
||||
.contains("short text");
|
||||
assertThat(multiPart.getParameter("longText"))
|
||||
.isEqualTo("very long text");
|
||||
assertThat(multiPart.getParameter("shortText"))
|
||||
.isEqualTo("short text");
|
||||
assertThat(multiPart.getParameterValues("multi"))
|
||||
.containsOnly("multi1", "multi2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unableParseRequest() throws IOException {
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4");
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
multiPart.parse(mockRequest, tempDir);
|
||||
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadException");
|
||||
}
|
||||
|
||||
protected String formFile(String fieldName, String filename, String content) {
|
||||
return endline +
|
||||
"--" + boundary + endline +
|
||||
"Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + filename + "\"" +
|
||||
endline +
|
||||
"Content-Type: text/csv" +
|
||||
endline +
|
||||
endline +
|
||||
content;
|
||||
}
|
||||
|
||||
protected String formField(String fieldName, String content) {
|
||||
return endline +
|
||||
"--" + boundary + endline +
|
||||
"Content-Disposition: form-data; name=\"" + fieldName + "\"" +
|
||||
endline +
|
||||
endline +
|
||||
content;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class JakartaMultiPartRequestTest extends AbstractMultiPartRequestTest {
|
||||
|
||||
@Override
|
||||
protected AbstractMultiPartRequest createMultipartRequest() {
|
||||
return new JakartaMultiPartRequest();
|
||||
}
|
||||
|
||||
}
|
||||
+44
-41
@@ -18,54 +18,57 @@
|
||||
*/
|
||||
package org.apache.struts2.dispatcher.multipart;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.struts2.dispatcher.LocalizedMessage;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.mock.web.DelegatingServletInputStream;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
public class JakartaStreamMultiPartRequestTest extends AbstractMultiPartRequestTest {
|
||||
|
||||
public class JakartaStreamMultiPartRequestTest {
|
||||
|
||||
private JakartaStreamMultiPartRequest multiPart;
|
||||
private Path tempDir;
|
||||
|
||||
@Before
|
||||
public void initialize() {
|
||||
multiPart = new JakartaStreamMultiPartRequest();
|
||||
tempDir = Paths.get("target", "multi-part-test");
|
||||
@Override
|
||||
protected AbstractMultiPartRequest createMultipartRequest() {
|
||||
return new JakartaStreamMultiPartRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of bytes in files greater than 2GB overflow the {@code int} primative.
|
||||
* The {@link HttpServletRequest#getContentLength()} returns {@literal -1}
|
||||
* when the header is not present or the size is greater than {@link Integer#MAX_VALUE}.
|
||||
*/
|
||||
@Test
|
||||
public void unknownContentLength() throws IOException {
|
||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||
Mockito.when(request.getContentType()).thenReturn("multipart/form-data; charset=utf-8; boundary=__X_BOUNDARY__");
|
||||
Mockito.when(request.getMethod()).thenReturn("POST");
|
||||
Mockito.when(request.getContentLength()).thenReturn(-1);
|
||||
String entity = "\r\n--__X_BOUNDARY__\r\n" +
|
||||
"Content-Disposition: form-data; name=\"upload\"; filename=\"test.csv\"\r\n" +
|
||||
"Content-Type: text/csv\r\n\r\n1,2\r\n\r\n" +
|
||||
"--__X_BOUNDARY__\r\n" +
|
||||
"Content-Disposition: form-data; name=\"upload2\"; filename=\"test2.csv\"\r\n" +
|
||||
"Content-Type: text/csv\r\n\r\n3,4\r\n\r\n" +
|
||||
"--__X_BOUNDARY__--\r\n";
|
||||
Mockito.when(request.getInputStream()).thenReturn(new DelegatingServletInputStream(new ByteArrayInputStream(entity.getBytes(StandardCharsets.UTF_8))));
|
||||
multiPart.setMaxSize("4");
|
||||
multiPart.parse(request, tempDir.toString());
|
||||
LocalizedMessage next = multiPart.getErrors().iterator().next();
|
||||
Assert.assertEquals(next.getTextKey(), "struts.messages.upload.error.FileUploadSizeException");
|
||||
public void maxSizeOfFiles() throws IOException {
|
||||
// given
|
||||
String content = formFile("file1", "test1.csv", "1,2,3,4") +
|
||||
formFile("file2", "test2.csv", "5,6,7,8") +
|
||||
endline + "--" + boundary + "--";
|
||||
|
||||
mockRequest.setContent(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(JakartaServletDiskFileUpload.isMultipartContent(mockRequest)).isTrue();
|
||||
|
||||
// when
|
||||
multiPart.setMaxSizeOfFiles("10");
|
||||
multiPart.parse(mockRequest, tempDir.toString());
|
||||
|
||||
// then
|
||||
assertThat(multiPart.uploadedFiles)
|
||||
.hasSize(1);
|
||||
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
|
||||
assertThat(file.isFile())
|
||||
.isTrue();
|
||||
assertThat(file.getOriginalName())
|
||||
.isEqualTo("test1.csv");
|
||||
assertThat(file.getContentType())
|
||||
.isEqualTo("text/csv");
|
||||
assertThat(file.getContent())
|
||||
.asInstanceOf(InstanceOfAssertFactories.FILE)
|
||||
.exists()
|
||||
.content()
|
||||
.isEqualTo("1,2,3,4");
|
||||
});
|
||||
assertThat(multiPart.getErrors())
|
||||
.map(LocalizedMessage::getTextKey)
|
||||
.containsExactly("struts.messages.upload.error.FileUploadSizeException");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+87
-107
@@ -25,16 +25,15 @@ import com.opensymphony.xwork2.ValidationAwareSupport;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import com.opensymphony.xwork2.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletDiskFileUpload;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
|
||||
import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
import org.assertj.core.util.Files;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.io.File;
|
||||
@@ -52,7 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
public static final UploadedFile EMPTY_FILE = new UploadedFile() {
|
||||
private static final UploadedFile EMPTY_FILE = new UploadedFile() {
|
||||
@Override
|
||||
public Long length() {
|
||||
return 0L;
|
||||
@@ -79,8 +78,8 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return new byte[0];
|
||||
public File getContent() {
|
||||
return Files.newTemporaryFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,6 +93,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
};
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private ActionFileUploadInterceptor interceptor;
|
||||
private File tempDir;
|
||||
|
||||
@@ -221,10 +221,10 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
String msg = errors.get(0);
|
||||
// the error message should contain at least this test
|
||||
assertThat(msg).contains(
|
||||
"The file is too large to be uploaded",
|
||||
"inputName",
|
||||
"log4j2.xml",
|
||||
"allowed mx size is 10"
|
||||
"The file is too large to be uploaded",
|
||||
"inputName",
|
||||
"log4j2.xml",
|
||||
"allowed mx size is 10"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,10 +245,8 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testInvalidContentTypeMultipartRequest() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
|
||||
req.setContentType("multipart/form-data"); // not a multipart contentype
|
||||
req.setMethod("post");
|
||||
request.setContentType("multipart/form-data"); // not a multipart contentype
|
||||
request.setMethod("post");
|
||||
|
||||
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
@@ -256,7 +254,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -277,7 +275,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -285,10 +283,9 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testSuccessUploadOfATextFileMultipartRequest() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("""
|
||||
@@ -299,7 +296,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
Unit test of ActionFileUploadInterceptor\r
|
||||
-----1234--\r
|
||||
""");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileUploadAction action = new MyFileUploadAction();
|
||||
|
||||
@@ -307,7 +304,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setAction(action);
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -322,27 +319,20 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
|
||||
* Tests whether with multiple files sent with the same name, the ones with forbiddenTypes (see
|
||||
* ActionFileUploadInterceptor.setAllowedTypes(...) ) are sorted out.
|
||||
*/
|
||||
public void testMultipleAccept() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("POST");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("POST");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=\"" + boundary + "\"");
|
||||
String content = encodeTextFile("test.html", "text/plain", plainContent) +
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
endline +
|
||||
endline +
|
||||
"--" +
|
||||
boundary +
|
||||
"--" +
|
||||
endline;
|
||||
req.setContent(content.getBytes());
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
endline + "--" + boundary + "--";
|
||||
request.setContent(content.getBytes());
|
||||
|
||||
assertTrue(JakartaServletDiskFileUpload.isMultipartContent(req));
|
||||
assertTrue(JakartaServletDiskFileUpload.isMultipartContent(request));
|
||||
|
||||
MyFileUploadAction action = new MyFileUploadAction();
|
||||
container.inject(action);
|
||||
@@ -350,7 +340,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setAction(action);
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.setAllowedTypes("text/html");
|
||||
interceptor.intercept(mai);
|
||||
@@ -364,22 +354,21 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testUnacceptedNumberOfFiles() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("POST");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("POST");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
String content = encodeTextFile("test.html", "text/plain", plainContent) +
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test3.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
"--" +
|
||||
boundary +
|
||||
"--" +
|
||||
endline;
|
||||
req.setContent(content.getBytes());
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test3.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
"--" +
|
||||
boundary +
|
||||
"--" +
|
||||
endline;
|
||||
request.setContent(content.getBytes());
|
||||
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(req));
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(request));
|
||||
|
||||
MyFileUploadAction action = new MyFileUploadAction();
|
||||
container.inject(action);
|
||||
@@ -387,7 +376,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setAction(action);
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxFiles(req));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxFiles());
|
||||
|
||||
interceptor.setAllowedTypes("text/html");
|
||||
interceptor.intercept(mai);
|
||||
@@ -401,10 +390,9 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testMultipartRequestMaxFileSize() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("""
|
||||
@@ -415,7 +403,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
Unit test of ActionFileUploadInterceptor\r
|
||||
-----1234--\r
|
||||
""");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
|
||||
|
||||
@@ -424,7 +412,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext()
|
||||
.withServletRequest(createMultipartRequestMaxFileSize(req));
|
||||
.withServletRequest(createMultipartRequestMaxFileSize());
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -435,15 +423,14 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
String msg = errors.iterator().next();
|
||||
// FIXME: the expected size is 40 - length of the string
|
||||
assertEquals(
|
||||
"File deleteme.txt assigned to file exceeded allowed size limit! Max size allowed is: 10 but file was: 10!",
|
||||
msg);
|
||||
"File deleteme.txt assigned to file exceeded allowed size limit! Max size allowed is: 10 but file was: 11!",
|
||||
msg);
|
||||
}
|
||||
|
||||
public void testMultipartRequestMaxStringLength() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("""
|
||||
@@ -462,7 +449,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
long string should not work\r
|
||||
-----1234--\r
|
||||
""");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
|
||||
|
||||
@@ -471,7 +458,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext()
|
||||
.withServletRequest(createMultipartRequestMaxStringLength(req));
|
||||
.withServletRequest(createMultipartRequestMaxStringLength());
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -481,15 +468,14 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertEquals(1, errors.size());
|
||||
String msg = errors.iterator().next();
|
||||
assertEquals(
|
||||
"The request parameter \"normalFormField2\" was too long. Max length allowed is 20, but found 27!",
|
||||
msg);
|
||||
"The request parameter \"normalFormField2\" was too long. Max length allowed is 20, but found 27!",
|
||||
msg);
|
||||
}
|
||||
|
||||
public void testMultipartRequestLocalizedError() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("""
|
||||
@@ -500,7 +486,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
Unit test of ActionFileUploadInterceptor\r
|
||||
-----1234--\r
|
||||
""");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
|
||||
|
||||
@@ -509,8 +495,8 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
ActionContext.getContext()
|
||||
.withLocale(Locale.GERMAN)
|
||||
.withServletRequest(createMultipartRequestMaxSize(req, 10));
|
||||
.withLocale(Locale.GERMAN)
|
||||
.withServletRequest(createMultipartRequestMaxSize(10));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -524,60 +510,54 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
private String encodeTextFile(String filename, String contentType, String content) {
|
||||
return "\r\n" +
|
||||
"--" +
|
||||
"simple boundary" +
|
||||
"\r\n" +
|
||||
"Content-Disposition: form-data; name=\"" +
|
||||
"file" +
|
||||
"\"; filename=\"" +
|
||||
filename +
|
||||
"\r\n" +
|
||||
"Content-Type: " +
|
||||
contentType +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
content;
|
||||
return endline +
|
||||
"--" + boundary +
|
||||
endline +
|
||||
"Content-Disposition: form-data; name=\"" + "file" + "\"; filename=\"" + filename +
|
||||
endline +
|
||||
"Content-Type: " + contentType +
|
||||
endline +
|
||||
endline +
|
||||
content;
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFileSize(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, 10, -1, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFileSize() {
|
||||
return createMultipartRequest(-1, 10, -1, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFiles(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, -1, 3, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFiles() {
|
||||
return createMultipartRequest(-1, -1, 3, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxSize(HttpServletRequest req, int maxsize) {
|
||||
return createMultipartRequest(req, maxsize, -1, -1, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxSize(int maxsize) {
|
||||
return createMultipartRequest(maxsize, -1, -1, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxStringLength(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, -1, -1, 20);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxStringLength() {
|
||||
return createMultipartRequest(-1, -1, -1, 20);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequest(int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
|
||||
JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
|
||||
jak.setMaxSize(String.valueOf(maxsize));
|
||||
jak.setMaxFileSize(String.valueOf(maxfilesize));
|
||||
jak.setMaxFiles(String.valueOf(maxfiles));
|
||||
jak.setMaxStringLength(String.valueOf(maxStringLength));
|
||||
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
|
||||
jak.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
interceptor = new ActionFileUploadInterceptor();
|
||||
container.inject(interceptor);
|
||||
tempDir = File.createTempFile("struts", "fileupload");
|
||||
tempDir.delete();
|
||||
tempDir.mkdirs();
|
||||
assertThat(tempDir.delete()).isTrue();
|
||||
assertThat(tempDir.mkdirs()).isTrue();
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
tempDir.delete();
|
||||
interceptor.destroy();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
+164
-135
@@ -18,8 +18,25 @@
|
||||
*/
|
||||
package org.apache.struts2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
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.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.action.UploadedFilesAware;
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
|
||||
import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
import org.assertj.core.util.Files;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -29,35 +46,14 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.TestAction;
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
import org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
|
||||
import org.apache.struts2.dispatcher.multipart.StrutsUploadedFile;
|
||||
import org.apache.struts2.dispatcher.multipart.UploadedFile;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
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.util.ClassLoaderUtil;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test case for FileUploadInterceptor.
|
||||
*/
|
||||
|
||||
public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
public static final UploadedFile EMPTY_FILE = new UploadedFile() {
|
||||
private static final UploadedFile EMPTY_FILE = new UploadedFile() {
|
||||
@Override
|
||||
public Long length() {
|
||||
return 0L;
|
||||
@@ -84,8 +80,8 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return new byte[0];
|
||||
public File getContent() {
|
||||
return Files.newTemporaryFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,6 +97,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
private FileUploadInterceptor interceptor;
|
||||
private File tempDir;
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
public void testAcceptFileWithEmptyAllowedTypesAndExtensions() {
|
||||
// when allowed type is empty
|
||||
@@ -182,9 +179,6 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testAcceptFileWithNoFile() {
|
||||
FileUploadInterceptor interceptor = new FileUploadInterceptor();
|
||||
interceptor.setContainer(container);
|
||||
|
||||
interceptor.setAllowedTypes("text/plain");
|
||||
|
||||
// when file is not of allowed types
|
||||
@@ -210,7 +204,11 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
URL url = ClassLoaderUtil.getResource("log4j2.xml", FileUploadInterceptorTest.class);
|
||||
File file = new File(new URI(url.toString()));
|
||||
assertTrue("log4j2.xml should be in src/test folder", file.exists());
|
||||
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file).withContentType("text/html").withOriginalName("filename").build();
|
||||
UploadedFile uploadedFile = StrutsUploadedFile.Builder.create(file)
|
||||
.withContentType("text/html")
|
||||
.withOriginalName("filename")
|
||||
.build();
|
||||
|
||||
boolean notOk = interceptor.acceptFile(validation, uploadedFile, "filename", "text/html", "inputName");
|
||||
|
||||
assertFalse(notOk);
|
||||
@@ -221,10 +219,10 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
String msg = errors.get(0);
|
||||
// the error message should contain at least this test
|
||||
assertThat(msg).contains(
|
||||
"The file is too large to be uploaded",
|
||||
"inputName",
|
||||
"log4j2.xml",
|
||||
"allowed mx size is 10"
|
||||
"The file is too large to be uploaded",
|
||||
"inputName",
|
||||
"log4j2.xml",
|
||||
"allowed mx size is 10"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -241,10 +239,8 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testInvalidContentTypeMultipartRequest() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
|
||||
req.setContentType("multipart/form-data"); // not a multipart contentype
|
||||
req.setMethod("post");
|
||||
request.setContentType("multipart/form-data"); // not a multipart contentype
|
||||
request.setMethod("post");
|
||||
|
||||
MyFileupAction action = container.inject(MyFileupAction.class);
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
@@ -253,7 +249,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
|
||||
ActionContext.getContext().withParameters(HttpParameters.create().build());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -261,12 +257,10 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testNoContentMultipartRequest() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data");
|
||||
req.setContent(null); // there is no content
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data");
|
||||
request.setContent(null); // there is no content
|
||||
|
||||
MyFileupAction action = container.inject(MyFileupAction.class);
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
@@ -275,7 +269,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
|
||||
ActionContext.getContext().withParameters(HttpParameters.create().build());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -283,10 +277,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testSuccessUploadOfATextFileMultipartRequest() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("-----1234\r\n" +
|
||||
@@ -296,7 +289,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
"Unit test of FileUploadInterceptor" +
|
||||
"\r\n" +
|
||||
"-----1234--\r\n");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileupAction action = new MyFileupAction();
|
||||
|
||||
@@ -306,7 +299,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext().withParameters(HttpParameters.create(param).build());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -338,23 +331,22 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
final String bondary = "simple boundary";
|
||||
final String endline = "\r\n";
|
||||
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("POST");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=" + bondary);
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("POST");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=" + bondary);
|
||||
String content = encodeTextFile("test.html", "text/plain", plainContent) +
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
endline +
|
||||
endline +
|
||||
"--" +
|
||||
bondary +
|
||||
"--" +
|
||||
endline;
|
||||
req.setContent(content.getBytes());
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
endline +
|
||||
endline +
|
||||
"--" +
|
||||
bondary +
|
||||
"--" +
|
||||
endline;
|
||||
request.setContent(content.getBytes());
|
||||
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(req));
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(request));
|
||||
|
||||
MyFileupAction action = new MyFileupAction();
|
||||
container.inject(action);
|
||||
@@ -364,7 +356,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext().withParameters(HttpParameters.create(param).build());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxSize(req, 2000));
|
||||
ActionContext.getContext().withServletRequest(createMultipartRequestMaxSize(2000));
|
||||
|
||||
interceptor.setAllowedTypes("text/html");
|
||||
interceptor.intercept(mai);
|
||||
@@ -391,22 +383,21 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
final String boundary = "simple boundary";
|
||||
final String endline = "\r\n";
|
||||
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("POST");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("POST");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=" + boundary);
|
||||
String content = encodeTextFile("test.html", "text/plain", plainContent) +
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test3.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
"--" +
|
||||
boundary +
|
||||
"--" +
|
||||
endline;
|
||||
req.setContent(content.getBytes());
|
||||
encodeTextFile("test1.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test2.html", "text/html", htmlContent) +
|
||||
encodeTextFile("test3.html", "text/html", htmlContent) +
|
||||
endline +
|
||||
"--" +
|
||||
boundary +
|
||||
"--" +
|
||||
endline;
|
||||
request.setContent(content.getBytes());
|
||||
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(req));
|
||||
assertTrue(JakartaServletFileUpload.isMultipartContent(request));
|
||||
|
||||
MyFileupAction action = new MyFileupAction();
|
||||
container.inject(action);
|
||||
@@ -415,8 +406,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setResultCode("success");
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext().withParameters(HttpParameters.create(param).build());
|
||||
ActionContext.getContext().put(ServletActionContext.HTTP_REQUEST, createMultipartRequestMaxFiles(req));
|
||||
ActionContext.getContext()
|
||||
.withParameters(HttpParameters.create(param).build())
|
||||
.withServletRequest(createMultipartRequestMaxFiles());
|
||||
|
||||
interceptor.setAllowedTypes("text/html");
|
||||
interceptor.intercept(mai);
|
||||
@@ -431,10 +423,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testMultipartRequestMaxFileSize() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("-----1234\r\n" +
|
||||
@@ -444,7 +435,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
"Unit test of FileUploadInterceptor" +
|
||||
"\r\n" +
|
||||
"-----1234--\r\n");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileupAction action = container.inject(MyFileupAction.class);
|
||||
|
||||
@@ -455,7 +446,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext()
|
||||
.withParameters(HttpParameters.create(param).build())
|
||||
.withServletRequest(createMultipartRequestMaxFileSize(req));
|
||||
.withServletRequest(createMultipartRequestMaxFileSize());
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -466,15 +457,14 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
String msg = errors.iterator().next();
|
||||
// FIXME: the expected size is 40 - length of the string
|
||||
assertEquals(
|
||||
"File deleteme.txt assigned to file exceeded allowed size limit! Max size allowed is: 10 but file was: 10!",
|
||||
"File deleteme.txt assigned to file exceeded allowed size limit! Max size allowed is: 10 but file was: 11!",
|
||||
msg);
|
||||
}
|
||||
|
||||
public void testMultipartRequestMaxStringLength() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("-----1234\r\n" +
|
||||
@@ -494,7 +484,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
"long string should not work" +
|
||||
"\r\n" +
|
||||
"-----1234--\r\n");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileupAction action = container.inject(MyFileupAction.class);
|
||||
|
||||
@@ -505,7 +495,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext()
|
||||
.withParameters(HttpParameters.create(param).build())
|
||||
.withServletRequest(createMultipartRequestMaxStringLength(req));
|
||||
.withServletRequest(createMultipartRequestMaxStringLength());
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -520,10 +510,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
public void testMultipartRequestLocalizedError() throws Exception {
|
||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||
req.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
req.setMethod("post");
|
||||
req.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("-----1234\r\n" +
|
||||
@@ -533,7 +522,7 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
"Unit test of FileUploadInterceptor" +
|
||||
"\r\n" +
|
||||
"-----1234--\r\n");
|
||||
req.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileupAction action = container.inject(MyFileupAction.class);
|
||||
|
||||
@@ -543,9 +532,9 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
Map<String, Object> param = new HashMap<>();
|
||||
ActionContext.getContext()
|
||||
.withParameters(HttpParameters.create(param).build())
|
||||
.withLocale(Locale.GERMAN)
|
||||
.withServletRequest(createMultipartRequestMaxSize(req, 10));
|
||||
.withParameters(HttpParameters.create(param).build())
|
||||
.withLocale(Locale.GERMAN)
|
||||
.withServletRequest(createMultipartRequestMaxSize(10));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
@@ -558,71 +547,111 @@ public class FileUploadInterceptorTest extends StrutsInternalTestCase {
|
||||
assertTrue(msg.startsWith("Der Request übertraf die maximal erlaubte Größe"));
|
||||
}
|
||||
|
||||
public void testSkippingUploadedFileAware() throws Exception {
|
||||
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
request.setMethod("post");
|
||||
request.addHeader("Content-type", "multipart/form-data; boundary=---1234");
|
||||
|
||||
// inspired by the unit tests for jakarta commons fileupload
|
||||
String content = ("-----1234\r\n" +
|
||||
"Content-Disposition: form-data; name=\"file\"; filename=\"deleteme.txt\"\r\n" +
|
||||
"Content-Type: text/html\r\n" +
|
||||
"\r\n" +
|
||||
"Unit test of FileUploadInterceptor" +
|
||||
"\r\n" +
|
||||
"-----1234--\r\n");
|
||||
request.setContent(content.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
MyFileUploadAction action = container.inject(MyFileUploadAction.class);
|
||||
|
||||
MockActionInvocation mai = new MockActionInvocation();
|
||||
mai.setInvocationContext(ActionContext.getContext());
|
||||
mai.setAction(action);
|
||||
MockActionProxy map = new MockActionProxy();
|
||||
map.setActionName("uploadedFiles");
|
||||
mai.setProxy(map);
|
||||
ActionContext.getContext()
|
||||
.withParameters(HttpParameters.create(new HashMap<String, Object>()).build())
|
||||
.withServletRequest(createMultipartRequestMaxSize(10));
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(action.hasActionErrors());
|
||||
}
|
||||
|
||||
private String encodeTextFile(String filename, String contentType, String content) {
|
||||
return "\r\n" +
|
||||
"--" +
|
||||
"simple boundary" +
|
||||
"\r\n" +
|
||||
"Content-Disposition: form-data; name=\"" +
|
||||
"file" +
|
||||
"\"; filename=\"" +
|
||||
filename +
|
||||
"\r\n" +
|
||||
"Content-Type: " +
|
||||
contentType +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
content;
|
||||
"--" +
|
||||
"simple boundary" +
|
||||
"\r\n" +
|
||||
"Content-Disposition: form-data; name=\"" +
|
||||
"file" +
|
||||
"\"; filename=\"" +
|
||||
filename +
|
||||
"\r\n" +
|
||||
"Content-Type: " +
|
||||
contentType +
|
||||
"\r\n" +
|
||||
"\r\n" +
|
||||
content;
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFileSize(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, 10, -1, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFileSize() {
|
||||
return createMultipartRequest(-1, 10, -1, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFiles(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, -1, 3, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxFiles() {
|
||||
return createMultipartRequest(-1, -1, 3, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxSize(HttpServletRequest req, int maxsize) {
|
||||
return createMultipartRequest(req, maxsize, -1, -1, -1);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxSize(int maxsize) {
|
||||
return createMultipartRequest(maxsize, -1, -1, -1);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxStringLength(HttpServletRequest req) {
|
||||
return createMultipartRequest(req, -1, -1, -1, 20);
|
||||
private MultiPartRequestWrapper createMultipartRequestMaxStringLength() {
|
||||
return createMultipartRequest(-1, -1, -1, 20);
|
||||
}
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequest(HttpServletRequest req, int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
|
||||
|
||||
private MultiPartRequestWrapper createMultipartRequest(int maxsize, int maxfilesize, int maxfiles, int maxStringLength) {
|
||||
JakartaMultiPartRequest jak = new JakartaMultiPartRequest();
|
||||
jak.setMaxSize(String.valueOf(maxsize));
|
||||
jak.setMaxFileSize(String.valueOf(maxfilesize));
|
||||
jak.setMaxFiles(String.valueOf(maxfiles));
|
||||
jak.setMaxStringLength(String.valueOf(maxStringLength));
|
||||
return new MultiPartRequestWrapper(jak, req, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
|
||||
jak.setDefaultEncoding(StandardCharsets.UTF_8.name());
|
||||
return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
|
||||
}
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
interceptor = new FileUploadInterceptor();
|
||||
container.inject(interceptor);
|
||||
tempDir = File.createTempFile("struts", "fileupload");
|
||||
tempDir.delete();
|
||||
tempDir.mkdirs();
|
||||
assertThat(tempDir.delete()).isTrue();
|
||||
assertThat(tempDir.mkdirs()).isTrue();
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
tempDir.delete();
|
||||
interceptor.destroy();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public static class MyFileupAction extends ActionSupport {
|
||||
|
||||
private static final long serialVersionUID = 6255238895447968889L;
|
||||
|
||||
// no methods
|
||||
}
|
||||
|
||||
public static class MyFileUploadAction extends ActionSupport implements UploadedFilesAware {
|
||||
private List<UploadedFile> uploadedFiles;
|
||||
|
||||
@Override
|
||||
public void withUploadedFiles(List<UploadedFile> uploadedFiles) {
|
||||
this.uploadedFiles = uploadedFiles;
|
||||
}
|
||||
|
||||
public List<UploadedFile> getUploadFiles() {
|
||||
return this.uploadedFiles;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* 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.parameter;
|
||||
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import org.apache.struts2.dispatcher.HttpParameters;
|
||||
import org.apache.struts2.dispatcher.Parameter;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class StrutsParameterAnnotationTest {
|
||||
|
||||
private ParametersInterceptor parametersInterceptor;
|
||||
|
||||
private ThreadAllowlist threadAllowlist;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
parametersInterceptor = new ParametersInterceptor();
|
||||
parametersInterceptor.setRequireAnnotations(Boolean.TRUE.toString());
|
||||
|
||||
threadAllowlist = new ThreadAllowlist();
|
||||
parametersInterceptor.setThreadAllowlist(threadAllowlist);
|
||||
|
||||
NotExcludedAcceptedPatternsChecker checker = mock(NotExcludedAcceptedPatternsChecker.class);
|
||||
when(checker.isAccepted(anyString())).thenReturn(AcceptedPatternsChecker.IsAccepted.yes(""));
|
||||
when(checker.isExcluded(anyString())).thenReturn(NotExcludedAcceptedPatternsChecker.IsExcluded.no(new HashSet<>()));
|
||||
parametersInterceptor.setAcceptedPatterns(checker);
|
||||
parametersInterceptor.setExcludedPatterns(checker);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
threadAllowlist.clearAllowlist();
|
||||
}
|
||||
|
||||
private void testParameter(Object action, String paramName, boolean shouldContain) {
|
||||
Map<String, String[]> requestParamMap = new HashMap<>();
|
||||
requestParamMap.put(paramName, new String[]{"value"});
|
||||
HttpParameters httpParameters = HttpParameters.create(requestParamMap).build();
|
||||
|
||||
Map<String, Parameter> acceptedParameters = parametersInterceptor.toAcceptableParameters(httpParameters, action);
|
||||
|
||||
if (shouldContain) {
|
||||
assertThat(acceptedParameters).containsOnlyKeys(paramName);
|
||||
} else {
|
||||
assertThat(acceptedParameters).isEmpty();
|
||||
assertThat(threadAllowlist.getAllowlist()).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Class<?>> getParentClasses(Class<?> ...clazzes) {
|
||||
Set<Class<?>> set = new HashSet<>();
|
||||
for (Class<?> clazz : clazzes) {
|
||||
set.add(clazz);
|
||||
set.addAll(ClassUtils.getAllSuperclasses(clazz));
|
||||
set.addAll(ClassUtils.getAllInterfaces(clazz));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateStrAnnotated() {
|
||||
testParameter(new FieldAction(), "privateStr", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrAnnotated() {
|
||||
testParameter(new FieldAction(), "publicStr", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrNotAnnotated() {
|
||||
testParameter(new FieldAction(), "publicStrNotAnnotated", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privatePojoAnnotated() {
|
||||
testParameter(new FieldAction(), "privatePojo.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthZero() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthZero.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthOne() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthOne.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthOne_sqrBracket() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthOne['key']", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthOne_bracket() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthOne('key')", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthOne() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthOne.key.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthTwo() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthTwo.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthTwo() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthTwo.key.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthTwo_sqrBracket() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthTwo['key']['key']", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthTwo_bracket() {
|
||||
testParameter(new FieldAction(), "publicPojoDepthTwo('key')('key')", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privateStrAnnotatedMethod() {
|
||||
testParameter(new MethodAction(), "privateStr", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrAnnotatedMethod() {
|
||||
testParameter(new MethodAction(), "publicStr", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrNotAnnotatedMethod() {
|
||||
testParameter(new MethodAction(), "publicStrNotAnnotated", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void privatePojoAnnotatedMethod() {
|
||||
testParameter(new MethodAction(), "privatePojo.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthZeroMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoDepthZero.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthOneMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoDepthOne.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthOneMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoDepthOne.key.key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoDepthTwoMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoDepthTwo.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicNestedPojoDepthTwoMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoDepthTwo.key.key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoListDepthOne() {
|
||||
testParameter(new FieldAction(), "publicPojoListDepthOne[0].key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoListDepthTwo() {
|
||||
testParameter(new FieldAction(), "publicPojoListDepthTwo[0].key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(List.class, Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoMapDepthTwo() {
|
||||
testParameter(new FieldAction(), "publicPojoMapDepthTwo['a'].key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Map.class, String.class, Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoListDepthOneMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoListDepthOne[0].key", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoListDepthTwoMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoListDepthTwo[0].key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(List.class, Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicPojoMapDepthTwoMethod() {
|
||||
testParameter(new MethodAction(), "publicPojoMapDepthTwo['a'].key", true);
|
||||
assertThat(threadAllowlist.getAllowlist()).containsExactlyInAnyOrderElementsOf(getParentClasses(Map.class, String.class, Pojo.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrNotAnnotated_transitionMode() {
|
||||
parametersInterceptor.setRequireAnnotationsTransitionMode(Boolean.TRUE.toString());
|
||||
testParameter(new FieldAction(), "publicStrNotAnnotated", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publicStrNotAnnotatedMethod_transitionMode() {
|
||||
parametersInterceptor.setRequireAnnotationsTransitionMode(Boolean.TRUE.toString());
|
||||
testParameter(new MethodAction(), "publicStrNotAnnotated", true);
|
||||
}
|
||||
|
||||
|
||||
class FieldAction {
|
||||
@StrutsParameter
|
||||
private String privateStr;
|
||||
|
||||
@StrutsParameter
|
||||
public String publicStr;
|
||||
|
||||
public String publicStrNotAnnotated;
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
private Pojo privatePojo;
|
||||
|
||||
@StrutsParameter
|
||||
public Pojo publicPojoDepthZero;
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public Pojo publicPojoDepthOne ;
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public Pojo publicPojoDepthTwo;
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public List<Pojo> publicPojoListDepthOne;
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public List<Pojo> publicPojoListDepthTwo;
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public Map<String, Pojo> publicPojoMapDepthTwo;
|
||||
}
|
||||
|
||||
class MethodAction {
|
||||
|
||||
@StrutsParameter
|
||||
private void setPrivateStr(String str) {
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setPublicStr(String str) {
|
||||
}
|
||||
|
||||
public void setPublicStrNotAnnotated(String str) {
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
private Pojo getPrivatePojo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public Pojo getPublicPojoDepthZero() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter
|
||||
public void setPublicPojoDepthZero() {
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public Pojo getPublicPojoDepthOne() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public Pojo getPublicPojoDepthTwo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 1)
|
||||
public List<Pojo> getPublicPojoListDepthOne() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public List<Pojo> getPublicPojoListDepthTwo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@StrutsParameter(depth = 2)
|
||||
public Map<String, Pojo> getPublicPojoMapDepthTwo() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class Pojo {
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,12 @@
|
||||
*/
|
||||
package org.apache.struts2.views.jsp;
|
||||
|
||||
import jakarta.servlet.jsp.JspException;
|
||||
import jakarta.servlet.jsp.tagext.TagSupport;
|
||||
import org.apache.commons.collections.ListUtils;
|
||||
import org.springframework.mock.web.MockBodyContent;
|
||||
import org.springframework.mock.web.MockJspWriter;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
@@ -25,27 +31,19 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections.ListUtils;
|
||||
import org.springframework.mock.web.MockBodyContent;
|
||||
import org.springframework.mock.web.MockJspWriter;
|
||||
|
||||
import jakarta.servlet.jsp.JspException;
|
||||
import jakarta.servlet.jsp.tagext.TagSupport;
|
||||
|
||||
|
||||
/**
|
||||
* Test Case for Iterator Tag
|
||||
*
|
||||
*/
|
||||
public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
IteratorTag tag;
|
||||
|
||||
private IteratorTag tag;
|
||||
|
||||
public void testIteratingWithIdSpecified() throws Exception {
|
||||
List list = new ArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("one");
|
||||
list.add("two");
|
||||
list.add("three");
|
||||
@@ -107,12 +105,12 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
IteratorTag freshTag = new IteratorTag();
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testIteratingWithIdSpecified_clearTagStateSet() throws Exception {
|
||||
List list = new ArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("one");
|
||||
list.add("two");
|
||||
list.add("three");
|
||||
@@ -177,12 +175,12 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
freshTag.setPerformClearTagStateForTagPoolingServers(true);
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testIteratingWithIdSpecifiedAndNullElementOnCollection() throws Exception {
|
||||
List list = new ArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("one");
|
||||
list.add(null);
|
||||
list.add("three");
|
||||
@@ -227,12 +225,12 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
IteratorTag freshTag = new IteratorTag();
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testIteratingWithIdSpecifiedAndNullElementOnCollection_clearTagStateSet() throws Exception {
|
||||
List list = new ArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("one");
|
||||
list.add(null);
|
||||
list.add("three");
|
||||
@@ -280,7 +278,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
freshTag.setPerformClearTagStateForTagPoolingServers(true);
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
@@ -297,7 +295,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCollectionIterator() {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("test1");
|
||||
list.add("test2");
|
||||
list.add("test3");
|
||||
@@ -317,7 +315,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testMapIterator() {
|
||||
Foo foo = new Foo();
|
||||
HashMap map = new HashMap();
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("test1", "123");
|
||||
map.put("test2", "456");
|
||||
map.put("test3", "789");
|
||||
@@ -332,8 +330,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doStartTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
@@ -343,8 +340,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -354,8 +350,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -365,8 +360,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.SKIP_BODY, result);
|
||||
@@ -375,8 +369,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doEndTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_PAGE, result);
|
||||
@@ -385,13 +378,13 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
IteratorTag freshTag = new IteratorTag();
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testMapIterator_clearTagStateSet() {
|
||||
Foo foo = new Foo();
|
||||
HashMap map = new HashMap();
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("test1", "123");
|
||||
map.put("test2", "456");
|
||||
map.put("test3", "789");
|
||||
@@ -408,8 +401,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
result = tag.doStartTag();
|
||||
setComponentTagClearTagState(tag, true); // Ensure component tag state clearing is set true (to match tag).
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
@@ -419,8 +411,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -430,8 +421,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -441,8 +431,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.SKIP_BODY, result);
|
||||
@@ -451,8 +440,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doEndTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_PAGE, result);
|
||||
@@ -462,7 +450,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
freshTag.setPerformClearTagStateForTagPoolingServers(true);
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
@@ -480,8 +468,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doStartTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
@@ -493,15 +480,16 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
assertFalse(status.isLast());
|
||||
assertTrue(status.isFirst());
|
||||
assertEquals(0, status.getIndex());
|
||||
assertEquals("0", status.getIndexStr());
|
||||
assertEquals(1, status.getCount());
|
||||
assertEquals("1", status.getCountStr());
|
||||
assertTrue(status.isOdd());
|
||||
assertFalse(status.isEven());
|
||||
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -520,8 +508,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -540,8 +527,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doEndTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_PAGE, result);
|
||||
@@ -550,7 +536,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
IteratorTag freshTag = new IteratorTag();
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
@@ -570,8 +556,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
result = tag.doStartTag();
|
||||
setComponentTagClearTagState(tag, true); // Ensure component tag state clearing is set true (to match tag).
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
@@ -590,8 +575,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -610,8 +594,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -630,8 +613,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doEndTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_PAGE, result);
|
||||
@@ -641,7 +623,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
freshTag.setPerformClearTagStateForTagPoolingServers(true);
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
@@ -669,7 +651,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testEmptyCollection() {
|
||||
Foo foo = new Foo();
|
||||
foo.setList(new ArrayList());
|
||||
foo.setList(new ArrayList<>());
|
||||
|
||||
stack.push(foo);
|
||||
|
||||
@@ -695,17 +677,41 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
validateCounter(new Integer[]{0, 1, 2, 3, 4, 5});
|
||||
}
|
||||
|
||||
public void testCounterWithStackValues() throws JspException {
|
||||
public void testCounterWithDifferentLocale() throws JspException {
|
||||
stack.getActionContext().withLocale(new Locale("fa_IR"));
|
||||
tag.setVar("it");
|
||||
tag.setBegin("0");
|
||||
tag.setEnd("5");
|
||||
List<String> expectedValues = Arrays.asList("0", "1", "2", "3", "4", "5");
|
||||
|
||||
ArrayList<String> values = new ArrayList<>();
|
||||
try {
|
||||
int result = tag.doStartTag();
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
values.add((String) stack.findValue("it", String.class));
|
||||
} catch (JspException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
while (tag.doAfterBody() == TagSupport.EVAL_BODY_AGAIN) {
|
||||
values.add((String) stack.findValue("top", String.class));
|
||||
}
|
||||
|
||||
assertEquals(expectedValues.size(), values.size());
|
||||
assertEquals(expectedValues, values);
|
||||
}
|
||||
|
||||
public void testCounterWithStackValues() throws JspException {
|
||||
stack.getContext().put("begin", 0);
|
||||
stack.getContext().put("end", 5);
|
||||
tag.setBegin("%{#begin}");
|
||||
tag.setEnd("%{#end}");
|
||||
tag.setBegin("begin");
|
||||
tag.setEnd("end");
|
||||
validateCounter(new Integer[]{0, 1, 2, 3, 4, 5});
|
||||
}
|
||||
|
||||
public void testCounterWithList() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -723,7 +729,6 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithArray() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -738,7 +743,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithListNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -755,7 +760,6 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithArrayNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -768,7 +772,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithList2() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -786,7 +790,6 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithArray2() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -800,7 +803,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithListNoEnd2() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -815,9 +818,8 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
validateCounter(new String[]{"c", "d"});
|
||||
}
|
||||
|
||||
public void testCounterWithArrayNoEnd2() throws JspException {
|
||||
public void testCounterWithArrayNoEnd2() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -841,9 +843,9 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
validateCounter(new Integer[]{0, 2, 4});
|
||||
}
|
||||
|
||||
public void testCounterWithListAndStep() throws JspException {
|
||||
public void testCounterWithListAndStep() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -861,9 +863,8 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
validateCounter(new String[]{"a", "c"});
|
||||
}
|
||||
|
||||
public void testCounterWithArrayAndStep() throws JspException {
|
||||
public void testCounterWithArrayAndStep() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -879,7 +880,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithListAndStepNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -898,7 +899,6 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithArrayAndStepNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
foo.setArray(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
stack.push(foo);
|
||||
@@ -920,7 +920,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithListAndNegativeStep() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -940,7 +940,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithListAndNegativeStepNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -957,9 +957,9 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
validateCounter(new String[]{"d", "c", "b", "a"});
|
||||
}
|
||||
|
||||
public void testCounterWithArrayAndNegativeStep() throws JspException {
|
||||
public void testCounterWithArrayAndNegativeStep() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -979,7 +979,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
|
||||
public void testCounterWithArrayAndNegativeStepNoEnd() throws JspException {
|
||||
Foo foo = new Foo();
|
||||
ArrayList list = new ArrayList();
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
list.add("c");
|
||||
@@ -997,14 +997,13 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
}
|
||||
|
||||
protected void validateCounter(Object[] expectedValues) throws JspException {
|
||||
List values = new ArrayList();
|
||||
ArrayList<Object> values = new ArrayList<>();
|
||||
try {
|
||||
int result = tag.doStartTag();
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
values.add(stack.getRoot().peek());
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
while (tag.doAfterBody() == TagSupport.EVAL_BODY_AGAIN) {
|
||||
@@ -1012,7 +1011,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
}
|
||||
|
||||
assertEquals(expectedValues.length, values.size());
|
||||
ListUtils.isEqualList(Arrays.asList(expectedValues), values);
|
||||
assertTrue(ListUtils.isEqualList(Arrays.asList(expectedValues), values));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1035,8 +1034,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doStartTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
|
||||
@@ -1046,8 +1044,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -1057,8 +1054,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_BODY_AGAIN, result);
|
||||
@@ -1068,8 +1064,7 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doAfterBody();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.SKIP_BODY, result);
|
||||
@@ -1082,16 +1077,14 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
try {
|
||||
result = tag.doStartTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.SKIP_BODY, result);
|
||||
try {
|
||||
result = tag.doEndTag();
|
||||
} catch (JspException e) {
|
||||
e.printStackTrace();
|
||||
fail();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertEquals(TagSupport.EVAL_PAGE, result);
|
||||
@@ -1100,13 +1093,13 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
IteratorTag freshTag = new IteratorTag();
|
||||
freshTag.setPageContext(pageContext);
|
||||
assertFalse("Tag state after doEndTag() under default tag clear state is equal to new Tag with pageContext/parent set. " +
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
class Foo {
|
||||
private Collection list;
|
||||
private Map map;
|
||||
static class Foo {
|
||||
private Collection<String> list;
|
||||
private Map<String, String> map;
|
||||
private String[] array;
|
||||
|
||||
public void setArray(String[] array) {
|
||||
@@ -1117,19 +1110,19 @@ public class IteratorTagTest extends AbstractUITagTest {
|
||||
return array;
|
||||
}
|
||||
|
||||
public void setList(Collection list) {
|
||||
public void setList(Collection<String> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public Collection getList() {
|
||||
public Collection<String> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setMap(Map map) {
|
||||
public void setMap(Map<String, String> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public Map getMap() {
|
||||
public Map<String, String> getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,5 +29,6 @@
|
||||
<Root level="info">
|
||||
<AppenderRef ref="STDOUT"/>
|
||||
</Root>
|
||||
<Logger name="org.apache.struts2.dispatcher.multipart" level="debug"/>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-jakarta</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-freemarker-jakarta</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-jakarta</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-jakarta</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-sitemesh2-jakarta</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-jakarta</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-jakarta</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<artifactId>struts2-velocity-tools-view-jakarta</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven Start Up Batch script
|
||||
# Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
@@ -27,7 +27,6 @@
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@@ -54,7 +53,7 @@ fi
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
case "$(uname)" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
@@ -62,9 +61,9 @@ case "`uname`" in
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
@@ -72,68 +71,38 @@ esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
javaExecutable="$(which javac)"
|
||||
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
readLink=$(which readlink)
|
||||
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
@@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
||||
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
@@ -184,96 +150,99 @@ find_maven_basedir() {
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
# Remove \r in case we run on Windows within Git Bash
|
||||
# and check out the repository with auto CRLF management
|
||||
# enabled. Otherwise, we may read lines that are delimited with
|
||||
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||
# splitting rules.
|
||||
tr -s '\r\n' ' ' < "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
log() {
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||
log "$MAVEN_PROJECTBASEDIR"
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if [ -r "$wrapperJarPath" ]; then
|
||||
log "Found $wrapperJarPath"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
while IFS="=" read -r key value; do
|
||||
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||
safeValue=$(echo "$value" | tr -d '\r')
|
||||
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
log "Downloading from: $wrapperUrl"
|
||||
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
log "Found wget ... using wget"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
log "Found curl ... using curl"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
log "Falling back to using Java to download"
|
||||
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
javaSource=$(cygpath --path --windows "$javaSource")
|
||||
javaClass=$(cygpath --path --windows "$javaClass")
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
if [ -e "$javaSource" ]; then
|
||||
if [ ! -e "$javaClass" ]; then
|
||||
log " - Compiling MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
if [ -e "$javaClass" ]; then
|
||||
log " - Running MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -282,35 +251,58 @@ fi
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
wrapperSha256Sum=""
|
||||
while IFS="=" read -r key value; do
|
||||
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ -n "$wrapperSha256Sum" ]; then
|
||||
wrapperSha256Result=false
|
||||
if command -v sha256sum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
elif command -v shasum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||
exit 1
|
||||
fi
|
||||
if [ $wrapperSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
# shellcheck disable=SC2086 # safe args
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% (
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
||||
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
echo Downloading from: %WRAPPER_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
@@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% (
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
@@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% (
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
SET WRAPPER_SHA_256_SUM=""
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||
)
|
||||
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||
powershell -Command "&{"^
|
||||
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||
" exit 1;"^
|
||||
"}"^
|
||||
"}"
|
||||
if ERRORLEVEL 1 goto error
|
||||
)
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-async-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-cdi-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-config-browser-plugin</artifactId>
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ import org.apache.struts2.dispatcher.mapper.ActionMapper;
|
||||
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
|
||||
import org.apache.struts2.views.freemarker.FreemarkerManager;
|
||||
import org.apache.struts2.views.velocity.VelocityConstants;
|
||||
import org.apache.struts2.views.velocity.VelocityManager;
|
||||
import org.apache.struts2.views.velocity.VelocityManagerInterface;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -56,7 +56,7 @@ public class ShowBeansAction extends ActionNamesAction {
|
||||
bindings.put(ActionMapper.class.getName(), addBindings(container, ActionMapper.class, StrutsConstants.STRUTS_MAPPER_CLASS));
|
||||
bindings.put(MultiPartRequest.class.getName(), addBindings(container, MultiPartRequest.class, StrutsConstants.STRUTS_MULTIPART_PARSER));
|
||||
bindings.put(FreemarkerManager.class.getName(), addBindings(container, FreemarkerManager.class, StrutsConstants.STRUTS_FREEMARKER_MANAGER_CLASSNAME));
|
||||
bindings.put(VelocityManager.class.getName(), addBindings(container, VelocityManager.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME));
|
||||
bindings.put(VelocityManagerInterface.class.getName(), addBindings(container, VelocityManagerInterface.class, VelocityConstants.STRUTS_VELOCITY_MANAGER_CLASSNAME));
|
||||
bindings.put(UrlRenderer.class.getName(), addBindings(container, UrlRenderer.class, StrutsConstants.STRUTS_URL_RENDERER));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-convention-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jasperreports-plugin</artifactId>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>net.sf.jasperreports</groupId>
|
||||
<artifactId>jasperreports</artifactId>
|
||||
<version>6.20.6</version>
|
||||
<version>6.21.0</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<!-- not necessary to compile and it force dependency convergence issues -->
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-javatemplates-plugin</artifactId>
|
||||
|
||||
-1
@@ -33,7 +33,6 @@ public class FileHandler extends AbstractTagHandler implements TagGenerator {
|
||||
a.addDefaultToEmpty("name", params.get("name"))
|
||||
.add("type", "file")
|
||||
.addIfExists("size", params.get("size"))
|
||||
.addIfExists("value", params.get("nameValue"))
|
||||
.addIfTrue("disabled", params.get("disabled"))
|
||||
.addIfExists("accept", params.get("accept"))
|
||||
.addIfExists("tabindex", params.get("tabindex"))
|
||||
|
||||
+1
-2
@@ -28,7 +28,6 @@ public class FileTest extends AbstractCommonAttributesTest {
|
||||
|
||||
public void testRenderTextField() {
|
||||
tag.setName("name");
|
||||
tag.setValue("val1");
|
||||
tag.setSize("10");
|
||||
tag.setDisabled("true");
|
||||
tag.setAccept("accept_");
|
||||
@@ -43,7 +42,7 @@ public class FileTest extends AbstractCommonAttributesTest {
|
||||
map.putAll(tag.getParameters());
|
||||
theme.renderTag(getTagName(), context);
|
||||
String output = writer.getBuffer().toString();
|
||||
String expected = s("<input name='name' type='file' size='10' value='val1' disabled='disabled' accept='accept_' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
|
||||
String expected = s("<input name='name' type='file' size='10' disabled='disabled' accept='accept_' tabindex='1' id='id1' class='class1' style='style1' title='title'></input>");
|
||||
assertEquals(expected, output);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>7.0.0-M1</version>
|
||||
<version>7.0.0-M4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jfreechart-plugin</artifactId>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user