Compare commits

...

147 Commits

Author SHA1 Message Date
Lukasz Lenart aca2eb07ef [maven-release-plugin] prepare release STRUTS_7_0_0_M4 2024-04-19 08:38:19 +02:00
Lukasz Lenart 964590c9a9 Merge pull request #904 from apache/merge/master-to-7xx-2024-04-07
[2024-04-07] Merge master branch into 7.x.x
2024-04-19 08:24:10 +02:00
Lukasz Lenart e5e6145c36 Merge remote-tracking branch 'origin/master' into merge/master-to-7xx-2024-04-07
# Conflicts:
#	pom.xml
2024-04-07 07:58:50 +02:00
Lukasz Lenart 0f6d5dbb2f Merge pull request #903 from apache/fix/WW-5390-assembly
WW-5390 Fixes creating assembly and attaching sources when preparing a new release
2024-04-07 07:08:19 +02:00
Lukasz Lenart db725518ee WW-5390 Fixes creating assembly and attaching sources when preparing a new release 2024-04-06 19:36:14 +02:00
Lukasz Lenart ee38cd5b14 Merge pull request #902 from apache/dependabot/maven/log4j2.version-2.23.1
WW-5404 Bump log4j2.version from 2.21.1 to 2.23.1
2024-04-06 18:45:12 +02:00
dependabot[bot] 7a79576bb3 Bump log4j2.version from 2.21.1 to 2.23.1
Bumps `log4j2.version` from 2.21.1 to 2.23.1.

Updates `org.apache.logging.log4j:log4j-api` from 2.21.1 to 2.23.1

Updates `org.apache.logging.log4j:log4j-core` from 2.21.1 to 2.23.1

Updates `org.apache.logging.log4j:log4j-jcl` from 2.21.1 to 2.23.1

Updates `org.apache.logging.log4j:log4j-slf4j-impl` from 2.21.1 to 2.23.1

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.apache.logging.log4j:log4j-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.apache.logging.log4j:log4j-jcl
  dependency-type: direct:development
  update-type: version-update:semver-minor
- dependency-name: org.apache.logging.log4j:log4j-slf4j-impl
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-04 10:01:09 +00:00
Lukasz Lenart 5cf746f538 Merge pull request #901 from apache/fix/branch-protction
Uses proper context name in branch protection rule
2024-04-02 15:01:10 +02:00
Lukasz Lenart 689d17d274 Merge pull request #896 from apache/feature/WW-5402-autoload-classptah
WW-5402 Auto loads Tiles definitions from classpath
2024-04-02 08:53:49 +02:00
Lukasz Lenart c7ae614824 WW-5402 Auto loads Tiles definitions from classpath 2024-03-31 11:52:59 +02:00
Lukasz Lenart 273c3a1a99 Uses proper context name in branch protection rule 2024-03-31 11:36:49 +02:00
Lukasz Lenart c774e23ef9 Merge pull request #900 from apache/WW-5251-retrofit-compat
WW-5251 Fix deprecated interface method signature
2024-03-31 11:35:52 +02:00
Kusal Kithul-Godage 93c11aaf62 WW-5251 Fix deprecated interface method signature 2024-03-31 00:10:39 +11:00
Lukasz Lenart 66c1ac431f Merge pull request #899 from apache/feature/codeowners-review
Enables required review by codeowners
2024-03-30 10:33:53 +01:00
Lukasz Lenart 7281d7e2ee Merge pull request #898 from apache/WW-5251-retrofit-compat
WW-5251 Reinstate deleted interfaces with transparent compat
2024-03-30 08:52:41 +01:00
Lukasz Lenart 6950fcb75f Enables required review by codeowners 2024-03-30 08:36:50 +01:00
Kusal Kithul-Godage dc774c484f WW-5251 Reinstate RequestAware 2024-03-29 21:35:08 +11:00
Kusal Kithul-Godage 19698e1fd2 WW-5251 Fix ParameterAware 2024-03-29 21:01:35 +11:00
Kusal Kithul-Godage cda2a2b231 WW-5251 Reinstate deleted interfaces with transparent compat 2024-03-27 18:41:24 +11:00
Lukasz Lenart cdc931e26f Merge pull request #895 from apache/fix/file-uploads
Converts multiple file uploads example to use Action based upload
2024-03-24 09:06:46 +01:00
Lukasz Lenart db0bd43855 Converts multiple file uploads example to use Action based upload 2024-03-17 17:45:32 +01:00
Kusal Kithul-Godage 86aee4b0ea Merge pull request #894 from apache/WW-5364-npe
WW-5364 Fix potential NPE in XmlDocConfigurationProvider
2024-03-14 15:48:48 +11:00
Kusal Kithul-Godage 29422e47fe WW-5364 Fix potential NPE in XmlDocConfigurationProvider 2024-03-13 21:49:27 +11:00
Lukasz Lenart e08f637be9 Merge pull request #892 from apache/feature/WW-5401-logging
[WW-5401] Improves logging around wrapping request and detecting multipart request
2024-03-11 13:45:09 +01:00
Lukasz Lenart 4939d3cef8 Merge pull request #893 from gregh3269/fix-STRUTS_7_0_0_M3-upload-file-name
Fix original file upload to use actual file name rather than uploadxxx.tmp
2024-03-08 09:05:41 +01:00
Greg Huber ca5e82309c Fix file upload to use actual file name rather than uploadxxx.tmp 2024-03-07 06:08:31 +00:00
Lukasz Lenart ac6c88ad4b WW-5401 Uses same message approach
Co-authored-by: Sebastian Peters <sebastian.peters@gmail.com>
2024-03-06 14:36:11 +01:00
Lukasz Lenart c320181275 WW-5401 Fixes typo
Co-authored-by: Sebastian Peters <sebastian.peters@gmail.com>
2024-03-06 14:35:53 +01:00
Lukasz Lenart f459981022 WW-5401 Improves logging around wrapping request and detecting multipart request 2024-03-06 08:09:09 +01:00
Lukasz Lenart 6648cbdb23 Merge pull request #886 from apache/dependabot/maven/maven-surefire-plugin.version-3.2.5
Bump maven-surefire-plugin.version from 3.0.0-M7 to 3.2.5
2024-02-26 07:13:55 +01:00
dependabot[bot] ac6095d1ae Bump maven-surefire-plugin.version from 3.0.0-M7 to 3.2.5
Bumps `maven-surefire-plugin.version` from 3.0.0-M7 to 3.2.5.

Updates `org.apache.maven.surefire:surefire-junit47` from 3.0.0-M7 to 3.2.5

Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.0.0-M7 to 3.2.5
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M7...surefire-3.2.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.surefire:surefire-junit47
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 01:35:59 +00:00
Lukasz Lenart d3727070b8 [maven-release-plugin] prepare for next development iteration 2024-02-24 09:22:43 +01:00
Lukasz Lenart a35cfebf5a [maven-release-plugin] prepare release STRUTS_7_0_0_M3 2024-02-24 09:22:32 +01:00
Lukasz Lenart c5d760ce58 Merge pull request #877 from apache/merge-master-to-7xx-2024-02-16
Merge master to 7-x-x 2024-02-16
2024-02-21 08:05:18 +01:00
Lukasz Lenart 154132d469 Merge pull request #884 from apache/dependabot/maven/org.apache.commons-commons-compress-1.26.0
[WW-5399] Bump org.apache.commons:commons-compress from 1.25.0 to 1.26.0
2024-02-21 08:04:47 +01:00
dependabot[bot] 43f1197961 Bump org.apache.commons:commons-compress from 1.25.0 to 1.26.0
Bumps org.apache.commons:commons-compress from 1.25.0 to 1.26.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 00:18:54 +00:00
Lukasz Lenart 0c4e21145d Merge pull request #882 from apache/dependabot/maven/commons-validator-commons-validator-1.8.0
WW-5398 Bump commons-validator:commons-validator from 1.6 to 1.8.0
2024-02-19 07:31:18 +01:00
Lukasz Lenart 3aa63e8cbb Merge pull request #883 from apache/dependabot/maven/org.apache.maven.doxia-doxia-module-markdown-1.12.0
Bump org.apache.maven.doxia:doxia-module-markdown from 1.9.1 to 1.12.0
2024-02-19 07:29:23 +01:00
Lukasz Lenart 82491eff87 Merge pull request #880 from apache/dependabot/maven/slf4j.version-2.0.12
Bump slf4j.version from 2.0.11 to 2.0.12
2024-02-19 07:28:39 +01:00
Lukasz Lenart d9ed77d3f8 Merge pull request #879 from apache/dependabot/maven/org.apache.maven.doxia-doxia-core-1.12.0
Bump org.apache.maven.doxia:doxia-core from 1.9.1 to 1.12.0
2024-02-19 07:28:06 +01:00
dependabot[bot] 8283ded5e2 Bump org.apache.maven.doxia:doxia-module-markdown from 1.9.1 to 1.12.0
Bumps org.apache.maven.doxia:doxia-module-markdown from 1.9.1 to 1.12.0.

---
updated-dependencies:
- dependency-name: org.apache.maven.doxia:doxia-module-markdown
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 01:46:51 +00:00
dependabot[bot] 1ddf446f78 Bump commons-validator:commons-validator from 1.6 to 1.8.0
Bumps commons-validator:commons-validator from 1.6 to 1.8.0.

---
updated-dependencies:
- dependency-name: commons-validator:commons-validator
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 01:46:43 +00:00
dependabot[bot] 94b2ee7ce5 Bump slf4j.version from 2.0.11 to 2.0.12
Bumps `slf4j.version` from 2.0.11 to 2.0.12.

Updates `org.slf4j:slf4j-api` from 2.0.11 to 2.0.12

Updates `org.slf4j:slf4j-simple` from 2.0.11 to 2.0.12

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.slf4j:slf4j-simple
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 01:44:27 +00:00
dependabot[bot] 43d180f362 Bump org.apache.maven.doxia:doxia-core from 1.9.1 to 1.12.0
Bumps [org.apache.maven.doxia:doxia-core](https://github.com/apache/maven-doxia) from 1.9.1 to 1.12.0.
- [Release notes](https://github.com/apache/maven-doxia/releases)
- [Commits](https://github.com/apache/maven-doxia/compare/doxia-1.9.1...doxia-1.12.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.doxia:doxia-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 01:44:05 +00:00
Lukasz Lenart 93fa72ee13 Merge pull request #878 from apache/fix/build-status 2024-02-18 13:58:25 +01:00
Lukasz Lenart 9d2a7649c5 Updates link to build status on Jenkins 2024-02-17 08:31:57 +01:00
Lukasz Lenart c553c93fab Merge pull request #843 from apache/dependabot/maven/net.sf.jasperreports-jasperreports-6.21.0
WW-5397 Bump net.sf.jasperreports:jasperreports from 6.20.6 to 6.21.0
2024-02-16 09:43:26 +01:00
Lukasz Lenart 45b06dd69c Merge remote-tracking branch 'origin/master' into merge-master-to-7xx-2024-02-16
# Conflicts:
#	plugins/osgi/src/main/java/org/apache/struts2/osgi/OsgiConfigurationProvider.java
#	plugins/portlet/src/main/java/org/apache/struts2/portlet/result/PortletVelocityResult.java
#	plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/OldDecorator2NewStrutsVelocityDecorator.java
#	plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityDecoratorServlet.java
#	plugins/sitemesh/src/main/java/org/apache/struts2/sitemesh/VelocityPageFilter.java
#	pom.xml
2024-02-16 09:27:16 +01:00
Lukasz Lenart a84f85798b Merge pull request #876 from gregh3269/fix-for-WW-5396
s:file shows server/file location WW-5396
2024-02-16 08:29:54 +01:00
Lukasz Lenart 7edcac886b Merge pull request #873 from apache/fix/WW-5388-fileupload
[WW-5388] Fixes a few issues in Servlet 6 file upload usage
2024-02-14 17:09:07 +01:00
Greg Huber bb3edaec06 s:file shows server/file location WW-5396 2024-02-14 12:00:53 +00:00
Greg Huber 1c59b45a23 s:file shows server/file location WW-5396 2024-02-14 11:52:38 +00:00
Greg Huber de9aacef61 s:file shows server/file location WW-5396 2024-02-14 11:04:43 +00:00
Greg Huber e3ca240b70 s:file shows server/file location WW-5396 2024-02-14 10:50:50 +00:00
Lukasz Lenart 0a2861b8a3 Merge pull request #872 from aleksandr-m/feature/rest_encoding
WW-5394 Use request encoding
2024-02-13 16:18:04 +01:00
Lukasz Lenart 694b97cc3c Merge pull request #874 from apache/dependabot/maven/commons-logging-commons-logging-1.3.0
WW-5395 Bump commons-logging:commons-logging from 1.2 to 1.3.0
2024-02-12 07:44:20 +01:00
Lukasz Lenart 42e8ad1cc2 Merge pull request #875 from apache/dependabot/github_actions/actions/upload-artifact-4.3.1
Bump actions/upload-artifact from 4.3.0 to 4.3.1
2024-02-12 07:42:40 +01:00
dependabot[bot] e9eae281c3 Bump actions/upload-artifact from 4.3.0 to 4.3.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/26f96dfa697d77e81fd5907df203aa23a56210a8...5d5d22a31266ced268874388b861e4b58bb5c2f3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 01:53:45 +00:00
dependabot[bot] 07f0d1eddb Bump commons-logging:commons-logging from 1.2 to 1.3.0
Bumps commons-logging:commons-logging from 1.2 to 1.3.0.

---
updated-dependencies:
- dependency-name: commons-logging:commons-logging
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 01:50:29 +00:00
Lukasz Lenart f913c407e9 WW-5388 Fixes a few issues in Servlet 6 file upload usage
- removes duplicated validation messages where both interceptors have been used
- defines a proper error message on a general file upload exception
- removes generic definition in tests
2024-02-11 11:21:29 +01:00
Aleksandr Mashchenko 6d6097c729 WW-5394 Use request encoding 2024-02-10 15:48:06 +02:00
Lukasz Lenart d384c824bd Merge pull request #870 from sepe81/feature/update-log42j-for-java-version-after-8
[WW-5384] Update log4j2 to 2.22.1 and [WW-5393] caffeine to 3.1.8 including error_prone_annotations > 2.10.0 which depends on Java 11
2024-02-09 07:14:46 +01:00
Sebastian Peters d1413b01c0 Override version of error_prone_annotations
until caffeine updates from 2.10.0 to 2.23.0
2024-02-08 22:16:29 +01:00
Sebastian Peters f8fcdbde50 [WW-5393] Update caffeine to 3.1.8 2024-02-08 22:16:11 +01:00
Sebastian Peters ade8af3772 [WW-5384] Update log4j2 to 2.22.1
and error_prone_annotations > 2.10.0 which depends on Java 11
2024-02-05 11:28:36 +01:00
Sebastian Peters caa34a4972 Update log4j2 to 2.21.1
(cherry picked from commit fd2cf91601)
2024-02-05 11:26:30 +01:00
Lukasz Lenart e6d1f59de7 Merge pull request #869 from apache/fix/temp-folder
Fixes problem with failing file upload tests
2024-02-05 08:55:54 +01:00
Lukasz Lenart 9afb24bdac Ensures if temp folder is writable 2024-02-05 08:06:58 +01:00
Lukasz Lenart f0db577e99 Merge pull request #868 from apache/fix/WW-5390-assembly
[WW-5390] Fixes generating assemblies during release process
2024-02-05 07:09:33 +01:00
Kusal Kithul-Godage 4aa1cbc56b Merge pull request #867 from apache/WW-5391-velocity-ext-point
WW-5391 Add interface for VelocityManager extension point
2024-02-05 13:39:08 +11:00
Lukasz Lenart 245eb90aa8 WW-5390 Fixes generating assemblies during release process 2024-02-03 18:09:36 +01:00
Lukasz Lenart ce24675221 Merge pull request #863 from sepe81/feature/dependency-updates
various dependency updates for master
2024-02-03 16:04:35 +01:00
Kusal Kithul-Godage 889a4180cd WW-5391 Fix serialisation warnings 2024-02-03 13:18:02 +11:00
Kusal Kithul-Godage 93f0a5116b WW-5391 Fix bean definition 2024-02-02 17:48:14 +11:00
Kusal Kithul-Godage b5fca66ad0 WW-5391 Migrate other usages 2024-02-02 17:42:56 +11:00
Kusal Kithul-Godage ace6a713c4 WW-5391 Fix VelocityDecoratorServlet 2024-02-02 17:34:58 +11:00
Kusal Kithul-Godage 9328ae7ec1 WW-5391 Add interface for VelocityManager extension point 2024-02-02 17:25:17 +11:00
Lukasz Lenart 737542a031 [maven-release-plugin] prepare for next development iteration 2024-02-01 10:39:59 +01:00
Lukasz Lenart 6db4c5150e [maven-release-plugin] prepare release STRUTS_7_0_0_M2 2024-02-01 10:39:50 +01:00
Lukasz Lenart 9a30a9f955 Merge pull request #866 from apache/merge-master-to-7xx-2024-02-01
Merge master into 7-0-x branch - 2024-02-01
2024-02-01 10:24:30 +01:00
Lukasz Lenart 11d84a26c3 Merge remote-tracking branch 'origin/master' into merge-master-to-7xx-2024-02-01
# Conflicts:
#	Jenkinsfile
#	core/src/main/java/org/apache/struts2/views/jsp/ComponentTagSupport.java
#	core/src/test/java/org/apache/struts2/views/jsp/IteratorTagTest.java
#	pom.xml
2024-02-01 10:02:43 +01:00
Lukasz Lenart 84d350dace Merge pull request #861 from apache/feature/WW-5388-upload-servlet6
[WW-5388] Uses the latest JakartaEE FileUpload Servlet 6 package
2024-02-01 09:23:26 +01:00
Lukasz Lenart e9e3c0a252 WW-5388 Adds dedicated integration test to cover max size upload 2024-01-31 09:45:22 +01:00
Lukasz Lenart d024ccdd5f WW-5388 Extracts method to read charset 2024-01-30 09:53:51 +01:00
Lukasz Lenart e2215c852d WW-5388 Introduces new constant to allow control max number of files 2024-01-29 19:08:56 +01:00
Lukasz Lenart eb469779f7 Merge pull request #864 from apache/dependabot/github_actions/actions/upload-artifact-4.3.0
Bump actions/upload-artifact from 4.2.0 to 4.3.0
2024-01-29 06:49:05 +01:00
dependabot[bot] 372aad2c6c Bump actions/upload-artifact from 4.2.0 to 4.3.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/694cdabd8bdb0f10b2cea11669e1bf5453eed0a6...26f96dfa697d77e81fd5907df203aa23a56210a8)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 01:48:45 +00:00
Lukasz Lenart e635b5973a WW-5388 Test multi-files and fields 2024-01-28 10:00:21 +01:00
Lukasz Lenart 51bf8de716 WW-5388 Uses temp directory to avoid problems on Jenkins 2024-01-28 09:47:23 +01:00
Lukasz Lenart b836072956 Merge pull request #852 from apache/fix/WW-5360-iterator
[WW-5360] Introduces additional countStr & indexStr to allow to ignore conversion
2024-01-28 09:31:40 +01:00
Lukasz Lenart d07792a40a WW-5388 Drops unused defaultLocale 2024-01-28 07:36:59 +01:00
Lukasz Lenart a358db5853 WW-5360 Introduces additional countStr & indexStr to allow to ignore conversion 2024-01-27 14:43:22 +01:00
Lukasz Lenart 3294ed08d1 WW-5388 Adds tests per each Jakarta parser 2024-01-27 11:20:30 +01:00
Sebastian Peters abd824fdd2 Update commons-text to 1.11.0 2024-01-27 11:10:16 +01:00
Sebastian Peters 218e634bf1 Update commons-io to 2.15.1 2024-01-27 11:09:42 +01:00
Sebastian Peters fd7e74867c Update commons-lang3 to 3.14.0 2024-01-27 11:09:12 +01:00
Sebastian Peters 27f0b56966 Update maven-enforcer-plugin to 3.4.1 2024-01-27 11:06:15 +01:00
Sebastian Peters 8ef55db35f Update maven-dependency-plugin to 3.6.1 2024-01-27 11:05:31 +01:00
Sebastian Peters fd2cf91601 Update log4j2 to 2.21.1 2024-01-27 10:18:30 +01:00
Sebastian Peters f8a8172a3e Update spring to 5.3.31 2024-01-26 12:49:46 +01:00
Sebastian Peters 71d28ed90d Update jackson to 2.16.1 2024-01-26 12:49:09 +01:00
Sebastian Peters 77285186df Update slf4j to 2.0.11 2024-01-26 12:48:45 +01:00
Sebastian Peters 028fedd306 Update assertj to 3.25.2 2024-01-26 12:45:17 +01:00
Sebastian Peters 8087ba97ee Update maven-site-plugin to 3.12.1 2024-01-26 12:45:01 +01:00
Sebastian Peters e299bba112 Update maven-war-plugin to 3.4.0 2024-01-26 12:44:36 +01:00
Kusal Kithul-Godage bd783a0e19 Merge pull request #832 from apache/WW-5352-parameter-annotation-3 2024-01-26 11:00:16 +11:00
Lukasz Lenart c37a6edb62 WW-5388 Avoids unconditional invocations of method in logs 2024-01-25 06:42:02 +01:00
Lukasz Lenart ac9b9ad89d WW-5388 Simplifies code 2024-01-24 21:40:15 +01:00
Lukasz Lenart b68b6ab7c6 WW-5388 Updates tests to match new logic 2024-01-24 21:20:18 +01:00
Lukasz Lenart 5057aeac83 Merge pull request #862 from apache/lukaszlenart-patch-1
Stops running sonar.yml on forks
2024-01-24 18:02:22 +01:00
Lukasz Lenart 7843cd5e1c Merge pull request #854 from sepe81/feature/update-security-policy
Small spelling and MD fixes (IntelliJ assisted)
2024-01-24 17:32:44 +01:00
Lukasz Lenart 2513fcb292 Stops running sonar.yml on forks 2024-01-24 17:28:52 +01:00
Sebastian Peters 644bd1f8ca Mention just the maintenance branches for supported versions
b/c https://struts.apache.org/releases.html#prior-releases
has further details.
2024-01-24 15:14:09 +01:00
Sebastian Peters e7a13b9637 Small spelling and MD fixes (IntelliJ assisted) 2024-01-24 15:14:01 +01:00
Lukasz Lenart 8c161f431d WW-5388 Uses the latest JakartaEE FileUpload Servlet 6 package
Also refactors the Jakarta based parsers as they have a lot in common
2024-01-24 09:09:56 +01:00
Lukasz Lenart 2de30e72a9 Merge pull request #859 from apache/fix/sped-up-build
Drops JDK11 build and fixes duplicated steps
2024-01-22 11:04:08 +01:00
Lukasz Lenart 65c0427eae Merge pull request #858 from apache/fix/plexus-exclusion
Fixes excluding Plexus container in OWASP scan
2024-01-22 10:09:58 +01:00
Lukasz Lenart 9f4b67a9a2 Drops JDK11 build and fixes duplicated steps 2024-01-22 10:08:59 +01:00
Lukasz Lenart cf74a4450c Fixes excluding Plexus container in OWASP scan 2024-01-22 09:28:14 +01:00
Lukasz Lenart ec18f0eef1 Merge pull request #855 from apache/dependabot/github_actions/actions/upload-artifact-4.2.0
Bump actions/upload-artifact from 4.1.0 to 4.2.0
2024-01-22 06:26:24 +01:00
dependabot[bot] cde86457ab Bump actions/upload-artifact from 4.1.0 to 4.2.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/1eb3cb2b3e0f29609092a73eb033bb759a334595...694cdabd8bdb0f10b2cea11669e1bf5453eed0a6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 01:38:19 +00:00
Lukasz Lenart 1e56b7ce68 Merge pull request #853 from sepe81/feature/upgrade-maven-to-3.9.6
Upgrade maven to 3.9.6 and wrapper to 3.2.0 (cherry-pick from 7.x)
2024-01-21 18:23:55 +01:00
Sebastian Peters 775febbdfa Upgrade maven to 3.9.6 and wrapper to 3.2.0
* `mvn wrapper:wrapper -Dmaven=3.9.6`
* remove outdated MavenWrapperDownloader from takari
* remove duplicate definition for maven-wrapper-plugin
  b/c it is already defined under pluginManagement

(cherry picked from commit 54a7c7094f)
2024-01-21 12:01:29 +01:00
Lukasz Lenart 77acd665ef Merge pull request #851 from sepe81/feature/upgrade-maven-to-3.9.6
Upgrade maven to 3.9.6 and wrapper to 3.2.0
2024-01-21 07:45:24 +01:00
Sebastian Peters 54a7c7094f Upgrade maven to 3.9.6 and wrapper to 3.2.0
* `mvn wrapper:wrapper -Dmaven=3.9.6`
* remove outdated MavenWrapperDownloader from takari
* remove duplicate definition for maven-wrapper-plugin
  b/c it is already defined under pluginManagement
2024-01-20 23:25:52 +01:00
Lukasz Lenart 00533db153 [maven-release-plugin] prepare for next development iteration 2024-01-20 11:39:08 +01:00
Kusal Kithul-Godage 71d77df3f3 WW-5352 Normalise parameter name 2024-01-18 21:24:36 +11:00
Kusal Kithul-Godage b50616942b WW-5352 Acceptance test coverage 2024-01-18 19:54:54 +11:00
Kusal Kithul-Godage 728d695ce1 WW-5352 Add debug logging for parameter rejections 2024-01-17 19:26:03 +11:00
dependabot[bot] ceb0a9a737 Bump net.sf.jasperreports:jasperreports from 6.20.6 to 6.21.0
Bumps [net.sf.jasperreports:jasperreports](https://github.com/TIBCOSoftware/jasperreports) from 6.20.6 to 6.21.0.
- [Release notes](https://github.com/TIBCOSoftware/jasperreports/releases)
- [Commits](https://github.com/TIBCOSoftware/jasperreports/commits)

---
updated-dependencies:
- dependency-name: net.sf.jasperreports:jasperreports
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 01:39:27 +00:00
Kusal Kithul-Godage 49b9c0c78c WW-5352 Ensure superclasses and interfaces allowlisted 2024-01-14 16:15:46 +11:00
Kusal Kithul-Godage 56d8361b41 WW-5352 Implement transition mode 2024-01-09 22:53:52 +11:00
Kusal Kithul-Godage bf7737fa07 WW-5352 Add unit test coverage for generics 2024-01-09 22:30:40 +11:00
Kusal Kithul-Godage f106b20983 WW-5352 Map-like type support 2024-01-09 21:23:18 +11:00
Kusal Kithul-Godage 6df80041e3 WW-5352 Auto allowlist parameterized types! 2024-01-09 20:54:15 +11:00
Kusal Kithul-Godage 770d311105 WW-5352 Mild optimisation 2024-01-09 20:23:44 +11:00
Kusal Kithul-Godage 0a71e2c3b9 WW-5352 Implement auto-allowlisting for Iterator component 2024-01-09 20:23:44 +11:00
Kusal Kithul-Godage a57c2882e7 WW-5352 Reinstate manual allowlist for generic types 2024-01-09 18:42:15 +11:00
Kusal Kithul-Godage b2c7542265 WW-5352 Dispatcher should up thread allowlist 2024-01-09 17:52:09 +11:00
Kusal Kithul-Godage 4c60f39c7a WW-5352 Enable annotations for showcase 2024-01-09 17:21:21 +11:00
Kusal Kithul-Godage 5d79301235 WW-5352 Fix missing curved bracket 2024-01-09 17:21:02 +11:00
Kusal Kithul-Godage e9154b922c Merge branch 'master' into WW-5352-parameter-annotation-3 2024-01-09 16:12:40 +11:00
Kusal Kithul-Godage 4c5f2b0266 WW-5352 Add full unit test coverage 2024-01-09 15:56:46 +11:00
Kusal Kithul-Godage bf3f407b5f WW-5352 Ensure allowlist is cleared if in unexpected state 2024-01-09 15:48:35 +11:00
Kusal Kithul-Godage 4255da3ee9 WW-5352 First draft implementation 2024-01-07 14:14:29 +11:00
Kusal Kithul-Godage ad576f0fd5 WW-5352 Introduce ThreadAllowlist bean 2024-01-07 14:13:23 +11:00
Kusal Kithul-Godage eca0666f0a WW-5352 Introduce StrutsParameter annotation 2024-01-07 14:13:23 +11:00
129 changed files with 4462 additions and 2012 deletions
+7 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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',
+1 -1
View File
@@ -17,7 +17,7 @@
The Apache Struts web framework
-------------------------------
[![Jenkins Build](https://builds.apache.org/buildStatus/icon?job=Struts%2FStruts+Core%2Fmaster)](https://ci-builds.apache.org/job/Struts/job/Struts%20Core/job/master/)
[![Build Status](https://ci-builds.apache.org/buildStatus/icon?job=Struts%2FStruts+Core%2Fmaster)](https://ci-builds.apache.org/job/Struts/job/Struts%20Core/job/master/)
[![Java Build](https://github.com/apache/struts/actions/workflows/maven.yml/badge.svg)](https://github.com/apache/struts/actions/workflows/maven.yml)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.struts/struts2-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.apache.struts/struts2-core/)
[![Javadocs](https://javadoc.io/badge/org.apache.struts/struts2-core.svg)](https://javadoc.io/doc/org.apache.struts/struts2-core)
+6 -6
View File
@@ -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
View File
@@ -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>
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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);
}
@@ -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;
}
@@ -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
@@ -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);
}
}
@@ -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>
+3 -11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
@@ -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);
}
@@ -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
* &lt;c:forEach begin="..." end="..." ...&gt; is to use some
* OGNL magic, which provides some under-the-covers magic to
* &lt;c:forEach begin="..." end="..." ...&gt; 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;
}
@@ -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<>();
}
}
}
@@ -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]", "_");
}
}
@@ -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
*
@@ -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);
}
}
@@ -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>
* &lt;s:iterator status="status" value='{0, 1}'&gt;
* Index: &lt;s:property value="%{#status.index}" /&gt; &lt;br /&gt;
* Count: &lt;s:property value="%{#status.count}" /&gt; &lt;br /&gt;
* Index Str: &lt;s:property value="%{#status.indexStr}" /&gt; &lt;br /&gt;
* Count: &lt;s:property value="%{#status.count}" /&gt; &lt;br /&gt;
* Count Str: &lt;s:property value="%{#status.countStr}" /&gt; &lt;br /&gt;
* &lt;/s:iterator&gt;
* </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}
+1
View File
@@ -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"));
@@ -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;
}
}
@@ -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();
}
}
@@ -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");
}
}
@@ -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();
}
@@ -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;
}
}
}
@@ -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;
}
}
+1
View File
@@ -29,5 +29,6 @@
<Root level="info">
<AppenderRef ref="STDOUT"/>
</Root>
<Logger name="org.apache.struts2.dispatcher.multipart" level="debug"/>
</Loggers>
</Configuration>
+1 -1
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
Vendored
+105 -113
View File
@@ -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 "$@"
Vendored
+24 -7
View File
@@ -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=%*
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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>
@@ -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));
}
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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 -->
+1 -1
View File
@@ -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>
@@ -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"))
@@ -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);
}
+1 -1
View File
@@ -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