Compare commits

..

153 Commits

Author SHA1 Message Date
Lukasz Lenart 4f8cb5211f [maven-release-plugin] prepare release STRUTS_7_0_0_M8 2024-07-11 09:48:14 +02:00
Lukasz Lenart b16ae04459 Merge pull request #984 from apache/fic/WW-5438-weld
WW-5438 Fixes scope of Weld dependencies
2024-07-11 08:26:12 +02:00
Lukasz Lenart 8c8bf6bc7d WW-5438 Fixes scope of Weld dependencies 2024-07-09 08:05:25 +02:00
Kusal Kithul-Godage 713fd3ba26 Merge pull request #983 from apache/7.0.x/merge-master-2024-07-09
Merge master to 7.0.x, 2024-07-09
2024-07-09 06:21:07 +10:00
Kusal Kithul-Godage d995b1a85e Merge remote-tracking branch 'origin/master' into 7.0.x/merge-master-2024-07-09 2024-07-09 05:45:36 +10:00
Kusal Kithul-Godage d4a0f4fdde Merge pull request #982 from apache/WW-5428-stop-excessive-logging
WW-5428 Stop excessive logging in DevMode
2024-07-09 05:44:33 +10:00
Kusal Kithul-Godage 9fe1a4a031 WW-5428 Stop excessive logging in DevMode 2024-07-09 05:01:02 +10:00
Kusal Kithul-Godage 70b5d7c83e Merge pull request #981 from apache/WW-5411-delete-deprecated-1
WW-5411 Delete deprecated method/classes
2024-07-08 21:29:32 +10:00
Lukasz Lenart 3693ea2eec Merge pull request #978 from apache/dependabot/github_actions/actions/upload-artifact-4.3.4
Bump actions/upload-artifact from 4.3.3 to 4.3.4
2024-07-08 13:19:12 +02:00
Lukasz Lenart 7cb57087d3 Merge pull request #976 from apache/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.3.0
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.2.5 to 3.3.0
2024-07-08 13:18:59 +02:00
Kusal Kithul-Godage b65005c72d Merge pull request #980 from apache/7.0.x/merge-master-2024-07-08
Merge master to 7.0.x, 2024-07-08
2024-07-08 21:18:46 +10:00
Kusal Kithul-Godage a3cc0422f9 WW-5411 Delete more deprecated methods from OgnlUtil 2024-07-08 20:40:02 +10:00
Kusal Kithul-Godage 089f620ea2 WW-5411 Delete deprecated Aware marker interfaces 2024-07-08 20:37:11 +10:00
Kusal Kithul-Godage 61d593620f WW-5411 Delete misc deprecated methods from 6.4.0 2024-07-08 20:37:11 +10:00
Kusal Kithul-Godage 31bdbfc27a WW-5411 Delete deprecated Velocity injection methods 2024-07-08 20:37:11 +10:00
Kusal Kithul-Godage 1b8e7ac824 WW-5411 Delete misc deprecated methods from 6.2.0 2024-07-08 20:37:11 +10:00
Kusal Kithul-Godage e3624835f0 WW-5411 Delete deprecated members from prepare filters 2024-07-08 20:37:11 +10:00
Kusal Kithul-Godage c801c8a741 WW-5411 Delete deprecated methods from OgnlUtil 2024-07-08 20:37:09 +10:00
Kusal Kithul-Godage ead66f2f75 WW-5411 Delete deprecated methods from cache factories and OgnlUtil constructor 2024-07-08 20:33:46 +10:00
Kusal Kithul-Godage 4944f1838f WW-5411 Delete deprecated methods from OgnlValueStack 2024-07-08 20:33:46 +10:00
Kusal Kithul-Godage e2d5cc2bd7 Merge branch 'refs/heads/master' into 7.0.x/merge-master-2024-07-08 2024-07-08 20:21:30 +10:00
Kusal Kithul-Godage 398e104fe0 Merge pull request #979 from apache/WW-5439-fix-dev-mode
WW-5439 Move DevMode security configuration to SecurityMemberAccess
2024-07-08 20:18:40 +10:00
Kusal Kithul-Godage 7f57e895a1 Merge pull request #967 from apache/WW-5428-allowlist-hibernate
WW-5428 Allowlist capability should resolve Hibernate proxies when disableProxyObjects is not set
2024-07-08 20:15:38 +10:00
Kusal Kithul-Godage 81b4943176 WW-5439 Move Dev Mode security configuration 2024-07-08 20:01:53 +10:00
Kusal Kithul-Godage 05680d7827 WW-5428 Amend log warning for missing allowlist entry 2024-07-08 19:57:36 +10:00
Kusal Kithul-Godage 8555dc266e WW-5428 Add log warning for allowlist disabled 2024-07-08 19:52:22 +10:00
Kusal Kithul-Godage c6f394a0e8 WW-5428 Add log warning for Hibernate entities 2024-07-08 19:44:05 +10:00
Kusal Kithul-Godage c965812ffe WW-5428 Add unit test coverage for Hibernate proxy resolution 2024-07-08 16:58:07 +10:00
Kusal Kithul-Godage abf03fdccc WW-5428 Clean up SecurityMemberAccessProxyTest 2024-07-08 16:58:07 +10:00
Kusal Kithul-Godage 2f814186c8 WW-5428 Allowlist capability should resolve Hibernate proxies when disableProxyObjects is not set 2024-07-08 16:58:07 +10:00
dependabot[bot] 7762d7fba2 Bump actions/upload-artifact from 4.3.3 to 4.3.4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/65462800fd760344b1a7b4382951275a0abb4808...0b2256b8c012f0828dc542b3febcab082c67f72b)

---
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-07-08 01:40:34 +00:00
Lukasz Lenart 82b364d521 Merge pull request #977 from stefansielaff/fix-behavior-of-envsvaluesubstitutor
WW-5437 Swap order of sysStrSubstitutor and envStrSubstitutor in substitute method
2024-07-05 06:52:12 +02:00
stefansielaff 98f2e68e0b "Swap order of sysStrSubstitutor and envStrSubstitutor in substitute method" 2024-07-02 13:24:57 +02:00
dependabot[bot] 40ab7a776a Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.2.5 to 3.3.0
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.2.5 to 3.3.0.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.5...surefire-3.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 01:20:28 +00:00
Lukasz Lenart 8b22f7170f Merge pull request #966 from apache/dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.3.0
Bump org.codehaus.mojo:exec-maven-plugin from 3.2.0 to 3.3.0
2024-06-24 07:46:26 +02:00
Lukasz Lenart 688413a359 Merge pull request #965 from apache/dependabot/maven/org.apache.maven.plugins-maven-enforcer-plugin-3.5.0
Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.4.1 to 3.5.0
2024-06-24 07:46:02 +02:00
Lukasz Lenart 53ed5f6dfa Merge pull request #960 from apache/dependabot/maven/org.apache.felix-org.apache.felix.main-7.0.5
Bump org.apache.felix:org.apache.felix.main from 6.0.3 to 7.0.5
2024-06-24 07:45:09 +02:00
Lukasz Lenart 100ef07a44 Merge pull request #958 from apache/dependabot/maven/commons-validator-commons-validator-1.9.0
Bump commons-validator:commons-validator from 1.8.0 to 1.9.0
2024-06-24 07:42:04 +02:00
Lukasz Lenart 4267bf0647 Merge pull request #971 from apache/feature/WW-5431-deprecated
WW-5431 Marks unused constants as deprecated
2024-06-24 07:40:13 +02:00
Lukasz Lenart 6cebeaca94 Merge pull request #970 from apache/feature/WW-5430-freemarker-jakarta
WW-5430 Uses Freemarker native support for JakartaEE instead of manually transforming the artifacts
2024-06-24 07:39:45 +02:00
Kusal Kithul-Godage 898a8d9ef9 Merge pull request #969 from apache/WW-5429-param-anno-log
WW-5429 Log parameter annotation issues at ERROR level when in DevMode
2024-06-21 19:47:23 +10:00
Lukasz Lenart 75ebbf4367 WW-5431 Marks unused constants as deprecated
To be removed in Struts 7
2024-06-21 08:24:54 +02:00
Lukasz Lenart a7145d695e WW-5430 Uses Freemarker native support for JakartaEE instead of manually transforming the artifacts 2024-06-21 08:13:07 +02:00
Lukasz Lenart a8954508c6 Merge pull request #968 from apache/fix/WW-5310-fragment
WW-5310 Fixes broken support for Fragments in <s:url/> tag
2024-06-21 07:28:17 +02:00
Kusal Kithul-Godage ba46c18f07 WW-5429 Make DebugUtils final and remove @author JavaDoc tag 2024-06-21 11:04:10 +10:00
Kusal Kithul-Godage b96cf2c072 WW-5429 Log parameter annotation issues at ERROR level when in DevMode 2024-06-18 19:36:51 +10:00
Lukasz Lenart 13916c8b84 WW-5310 Fixes broken support for Fragments in <s:url/> tag 2024-06-18 09:39:17 +02:00
dependabot[bot] a99162a1a4 Bump org.codehaus.mojo:exec-maven-plugin from 3.2.0 to 3.3.0
Bumps [org.codehaus.mojo:exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/3.2.0...3.3.0)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:exec-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 01:28:53 +00:00
dependabot[bot] b07268d5bd Bump org.apache.maven.plugins:maven-enforcer-plugin from 3.4.1 to 3.5.0
Bumps [org.apache.maven.plugins:maven-enforcer-plugin](https://github.com/apache/maven-enforcer) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/apache/maven-enforcer/releases)
- [Commits](https://github.com/apache/maven-enforcer/compare/enforcer-3.4.1...enforcer-3.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 01:28:48 +00:00
Lukasz Lenart 28a68676ec [maven-release-plugin] prepare for next development iteration 2024-06-12 07:45:22 +02:00
Lukasz Lenart 1057d4149b [maven-release-plugin] prepare release STRUTS_7_0_0_M7 2024-06-12 07:45:12 +02:00
Lukasz Lenart d1d02c1507 Merge pull request #963 from apache/merge/master-2024-06-10
Merge master 2024-06-10
2024-06-12 07:28:31 +02:00
Lukasz Lenart 28ea6d01c2 Merge branch 'master' into merge/master-2024-06-10 2024-06-10 07:30:59 +02:00
Lukasz Lenart 444e4d4c07 Merge pull request #957 from apache/fix/WW-5250-todo
WW-5250 Addresses TODO in test and stops using Mock Objects
2024-06-10 07:03:13 +02:00
Lukasz Lenart cf34f0dbc0 Merge pull request #956 from apache/feature/WW-5400-refactor
WW-5400 Simplifies how CspSettings is created
2024-06-10 07:02:49 +02:00
Lukasz Lenart 62eac8f58f Merge pull request #954 from apache/fix/WW-5423-query-params
[WW-5423] Fixes returning null instead of empty array in case of non-existing param
2024-06-10 07:02:27 +02:00
Lukasz Lenart cabc076e48 Merge pull request #962 from apache/dependabot/maven/org.owasp-dependency-check-maven-9.2.0
Bump org.owasp:dependency-check-maven from 8.4.2 to 9.2.0
2024-06-10 07:02:13 +02:00
Lukasz Lenart de2a8d9902 Merge pull request #961 from apache/dependabot/maven/org.apache.commons-commons-compress-1.26.2
Bump org.apache.commons:commons-compress from 1.26.0 to 1.26.2
2024-06-10 07:02:03 +02:00
dependabot[bot] 9133d24f69 Bump org.owasp:dependency-check-maven from 8.4.2 to 9.2.0
Bumps [org.owasp:dependency-check-maven](https://github.com/jeremylong/DependencyCheck) from 8.4.2 to 9.2.0.
- [Release notes](https://github.com/jeremylong/DependencyCheck/releases)
- [Changelog](https://github.com/jeremylong/DependencyCheck/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jeremylong/DependencyCheck/compare/v8.4.2...v9.2.0)

---
updated-dependencies:
- dependency-name: org.owasp:dependency-check-maven
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 01:57:52 +00:00
dependabot[bot] 86b14c7062 Bump org.apache.commons:commons-compress from 1.26.0 to 1.26.2
Bumps org.apache.commons:commons-compress from 1.26.0 to 1.26.2.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 01:57:34 +00:00
dependabot[bot] 54bf309f88 Bump org.apache.felix:org.apache.felix.main from 6.0.3 to 7.0.5
Bumps org.apache.felix:org.apache.felix.main from 6.0.3 to 7.0.5.

---
updated-dependencies:
- dependency-name: org.apache.felix:org.apache.felix.main
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 01:57:32 +00:00
dependabot[bot] 63fcf0f14f Bump commons-validator:commons-validator from 1.8.0 to 1.9.0
Bumps commons-validator:commons-validator from 1.8.0 to 1.9.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-06-10 01:57:26 +00:00
Lukasz Lenart 03db4f1ab0 WW-5250 Addresses TODO in test and stops using Mock Objects 2024-06-07 08:33:08 +02:00
Lukasz Lenart 4a05653a55 Removes duplication
Co-authored-by: Kusal Kithul-Godage <git@kusal.io>
2024-06-06 06:32:30 +02:00
Lukasz Lenart 3a6ad5a557 WW-5400 Simplifies how CspSettings is created 2024-06-05 07:39:59 +02:00
Lukasz Lenart 5015ea0d90 WW-5423 Fixes returning null instead of empty array in case of non-existing param 2024-06-05 07:07:26 +02:00
Lukasz Lenart dcaff0bf10 Merge pull request #948 from apache/feature/WW-5412-master
[WW-5412] Upgrades struts-master to ver 15
2024-06-05 07:06:01 +02:00
Lukasz Lenart a25173f65d Merge pull request #947 from apache/fix/no-reviewers-required
Disables required reviewers option
2024-06-05 07:05:33 +02:00
Lukasz Lenart 0cdce05745 Merge pull request #946 from apache/fix/WW-5424-class-cast-exception
[WW-5424] Fixes ClassCastException when using short var name in s:set tag
2024-06-05 07:05:11 +02:00
Lukasz Lenart ee040babd4 Merge pull request #953 from apache/dependabot/maven/org.freemarker-freemarker-2.3.33
WW-5426 Bump org.freemarker:freemarker from 2.3.32 to 2.3.33
2024-06-03 07:04:47 +02:00
Lukasz Lenart f6b25ac8aa Merge pull request #950 from apache/dependabot/maven/org.apache.maven.plugins-maven-assembly-plugin-3.7.1
Bump org.apache.maven.plugins:maven-assembly-plugin from 3.6.0 to 3.7.1
2024-06-03 07:02:27 +02:00
dependabot[bot] 5c2be7cc7d Bump org.freemarker:freemarker from 2.3.32 to 2.3.33
Bumps org.freemarker:freemarker from 2.3.32 to 2.3.33.

---
updated-dependencies:
- dependency-name: org.freemarker:freemarker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 01:37:13 +00:00
dependabot[bot] e5fcc8f175 Bump org.apache.maven.plugins:maven-assembly-plugin from 3.6.0 to 3.7.1
Bumps [org.apache.maven.plugins:maven-assembly-plugin](https://github.com/apache/maven-assembly-plugin) from 3.6.0 to 3.7.1.
- [Release notes](https://github.com/apache/maven-assembly-plugin/releases)
- [Commits](https://github.com/apache/maven-assembly-plugin/compare/maven-assembly-plugin-3.6.0...maven-assembly-plugin-3.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 01:34:52 +00:00
Lukasz Lenart 855b95e557 WW-5412 Upgrades struts-master to ver 15 2024-06-02 19:13:01 +02:00
Lukasz Lenart 00752e33c4 Disables required reviewers option
This option doesn't play with Silence Consensus
2024-06-02 13:59:41 +02:00
Lukasz Lenart 4a8ff99b1c WW-5424 Fixes ClassCastException when using short var name in s:set tag 2024-06-02 13:53:37 +02:00
Lukasz Lenart 9682b3b429 Merge pull request #944 from apache/dependabot/maven/jackson.version-2.17.1
WW-5425 Bump jackson.version from 2.16.1 to 2.17.1
2024-06-01 07:05:42 +02:00
Lukasz Lenart 8268d0b063 Merge pull request #940 from apache/dependabot/maven/org.jacoco-jacoco-maven-plugin-0.8.12
Bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12
2024-06-01 07:02:24 +02:00
Lukasz Lenart 36230dc5be Merge pull request #945 from apache/fix/disable-review
INFRA-25666 Disables review by code owners
2024-05-20 08:02:51 +02:00
Lukasz Lenart 9ee39d0a2c INFRA-25666 Disables review by code owners
* this functionality requires to have public teams
* currently all ASF teams are private
2024-05-20 07:02:45 +02:00
dependabot[bot] ab89b8287a Bump jackson.version from 2.16.1 to 2.17.1
Bumps `jackson.version` from 2.16.1 to 2.17.1.

Updates `com.fasterxml.jackson.core:jackson-core` from 2.16.1 to 2.17.1
- [Commits](https://github.com/FasterXML/jackson-core/compare/jackson-core-2.16.1...jackson-core-2.17.1)

Updates `com.fasterxml.jackson.core:jackson-databind` from 2.16.1 to 2.17.1
- [Commits](https://github.com/FasterXML/jackson/commits)

Updates `com.fasterxml.jackson.dataformat:jackson-dataformat-xml` from 2.16.1 to 2.17.1
- [Commits](https://github.com/FasterXML/jackson-dataformat-xml/compare/jackson-dataformat-xml-2.16.1...jackson-dataformat-xml-2.17.1)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-xml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 01:22:13 +00:00
dependabot[bot] 19252adea0 Bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12
Bumps [org.jacoco:jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.11 to 0.8.12.
- [Release notes](https://github.com/jacoco/jacoco/releases)
- [Commits](https://github.com/jacoco/jacoco/compare/v0.8.11...v0.8.12)

---
updated-dependencies:
- dependency-name: org.jacoco:jacoco-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 01:19:53 +00:00
Lukasz Lenart 40ccc74b8a Merge pull request #933 from apache/fix/WW-5415-constructor
WW-5415 Fixes accessing public constructors via expression
2024-05-14 19:56:27 +02:00
Lukasz Lenart d147543dcd Merge pull request #932 from apache/feature/WW-5414-after
WW-5414 Always call afterInvocation even in case of exception
2024-05-14 19:55:30 +02:00
Lukasz Lenart 49eda37f2d Merge pull request #931 from apache/fix/WW-5422-trimable
WW-5422 Fixes support for trimable locale string in request
2024-05-13 08:23:26 +02:00
Lukasz Lenart 0a57cac6d6 WW-5422 Adds dedicate unit test to cover DefaultLocaleProvider 2024-05-13 07:43:36 +02:00
Lukasz Lenart 195b0e59d0 Merge pull request #939 from apache/dependabot/github_actions/ossf/scorecard-action-2.3.3
Bump ossf/scorecard-action from 2.3.1 to 2.3.3
2024-05-13 06:50:39 +02:00
Lukasz Lenart 03126c01bf Merge pull request #938 from apache/dependabot/maven/org.apache.maven.plugins-maven-project-info-reports-plugin-3.5.0
Bump org.apache.maven.plugins:maven-project-info-reports-plugin from 3.0.0 to 3.5.0
2024-05-13 06:50:20 +02:00
Lukasz Lenart 73be382a96 Merge pull request #936 from apache/dependabot/maven/slf4j.version-2.0.13
Bump slf4j.version from 2.0.12 to 2.0.13
2024-05-13 06:49:43 +02:00
Lukasz Lenart f338fda6d8 Merge pull request #934 from apache/dependabot/maven/org.apache.maven.plugins-maven-source-plugin-3.3.1
Bump org.apache.maven.plugins:maven-source-plugin from 3.3.0 to 3.3.1
2024-05-13 06:48:23 +02:00
Kusal Kithul-Godage 7c523ac33e WW-5415 Constructor members should be exempted as static members 2024-05-13 12:37:04 +10:00
dependabot[bot] 039c5f8d42 Bump ossf/scorecard-action from 2.3.1 to 2.3.3
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.1 to 2.3.3.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/0864cf19026789058feabb7e87baa5f140aac736...dc50aa9510b46c811795eb24b2f1ba02a914e534)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 01:38:25 +00:00
dependabot[bot] 6e12c25ac0 Bump org.apache.maven.plugins:maven-project-info-reports-plugin
Bumps [org.apache.maven.plugins:maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 3.0.0 to 3.5.0.
- [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-3.0.0...maven-project-info-reports-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-project-info-reports-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 01:16:29 +00:00
dependabot[bot] c6b53d4887 Bump slf4j.version from 2.0.12 to 2.0.13
Bumps `slf4j.version` from 2.0.12 to 2.0.13.

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

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

---
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-05-13 01:16:15 +00:00
dependabot[bot] 942fbd67e7 Bump org.apache.maven.plugins:maven-source-plugin from 3.3.0 to 3.3.1
Bumps [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) from 3.3.0 to 3.3.1.
- [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.3.0...maven-source-plugin-3.3.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 01:15:24 +00:00
Lukasz Lenart b36e88ff4b WW-5415 Fixes accessing public constructors via expression 2024-05-12 09:47:38 +02:00
Lukasz Lenart 4dfbe09343 WW-5414 Always call afterInvocation even in case of exception 2024-05-11 10:22:27 +02:00
Lukasz Lenart 649760d8b0 Merge pull request #913 from eschulma/master
WW-5400 Extend default configuration options for the CSP interceptor.
2024-05-11 06:36:33 +02:00
Lukasz Lenart 49b8eedf1f Merge pull request #920 from apache/fix/WW-5419-tiles
[WW-5419] Fixes support for loading Tiles definitions
2024-05-06 18:20:54 +02:00
Lukasz Lenart c0d1217d7c Merge pull request #924 from apache/feature/WW-5420-text
WW-5420 Upgrades commons-text to ver. 1.12.0
2024-04-29 06:24:29 +02:00
Lukasz Lenart f2e93d017b Merge pull request #905 from apache/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.2.5
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.0.0-M6 to 3.2.5
2024-04-29 06:23:48 +02:00
Lukasz Lenart 3ed212a1f2 Merge pull request #907 from apache/dependabot/maven/asm.version-9.7
WW-5421 Bump asm.version from 9.6 to 9.7
2024-04-29 06:23:09 +02:00
Lukasz Lenart ed3786c6b0 Merge pull request #926 from apache/dependabot/github_actions/actions/upload-artifact-4.3.3
Bump actions/upload-artifact from 4.3.2 to 4.3.3
2024-04-29 06:21:16 +02:00
Lukasz Lenart 5cd9356049 Merge pull request #925 from apache/dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.2.0
Bump org.codehaus.mojo:exec-maven-plugin from 3.1.0 to 3.2.0
2024-04-29 06:20:52 +02:00
dependabot[bot] 2d9af47a90 Bump actions/upload-artifact from 4.3.2 to 4.3.3
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.2 to 4.3.3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/1746f4ab65b179e0ea60a494b83293b640dd5bba...65462800fd760344b1a7b4382951275a0abb4808)

---
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-04-29 01:46:50 +00:00
dependabot[bot] c1c6a463a8 Bump org.codehaus.mojo:exec-maven-plugin from 3.1.0 to 3.2.0
Bumps [org.codehaus.mojo:exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) from 3.1.0 to 3.2.0.
- [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/exec-maven-plugin-3.1.0...3.2.0)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:exec-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 01:15:19 +00:00
Lukasz Lenart 36275b9e04 WW-5420 Upgrades commons-text to ver. 1.12.0 2024-04-25 08:25:09 +02:00
Kusal Kithul-Godage baab7dd36c Merge pull request #919 from apache/WW-5353-stronger-security-defaults
WW-5353 Stronger security defaults for 7.0
2024-04-24 22:33:03 +10:00
Lukasz Lenart a88cb741d0 WW-5419 Fixes support for loading Tiles definitions 2024-04-22 06:49:44 +02:00
Lukasz Lenart 3cde668598 Merge pull request #922 from apache/dependabot/maven/org.codehaus.mojo-versions-maven-plugin-2.16.2
Bump org.codehaus.mojo:versions-maven-plugin from 2.16.1 to 2.16.2
2024-04-22 06:39:33 +02:00
Lukasz Lenart eca5b21088 Merge pull request #923 from apache/dependabot/github_actions/actions/upload-artifact-4.3.2
Bump actions/upload-artifact from 4.3.1 to 4.3.2
2024-04-22 06:36:59 +02:00
dependabot[bot] 856d98010a Bump actions/upload-artifact from 4.3.1 to 4.3.2
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/5d5d22a31266ced268874388b861e4b58bb5c2f3...1746f4ab65b179e0ea60a494b83293b640dd5bba)

---
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-04-22 01:35:16 +00:00
dependabot[bot] b2b35f066c Bump org.codehaus.mojo:versions-maven-plugin from 2.16.1 to 2.16.2
Bumps [org.codehaus.mojo:versions-maven-plugin](https://github.com/mojohaus/versions) from 2.16.1 to 2.16.2.
- [Release notes](https://github.com/mojohaus/versions/releases)
- [Changelog](https://github.com/mojohaus/versions/blob/master/ReleaseNotes.md)
- [Commits](https://github.com/mojohaus/versions/compare/2.16.1...2.16.2)

---
updated-dependencies:
- dependency-name: org.codehaus.mojo:versions-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 01:23:03 +00:00
Kusal Kithul-Godage 831689b26f Merge pull request #918 from apache/merge-master-to-70-2024-04-20
Forward merge master to 7.0.0
2024-04-21 00:54:12 +10:00
Lukasz Lenart efdaf7e50f Merge pull request #909 from apache/dependabot/maven/org.assertj-assertj-core-3.25.3
Bump org.assertj:assertj-core from 3.25.2 to 3.25.3
2024-04-20 15:59:26 +02:00
Kusal Kithul-Godage 5d4ad83ccd WW-5353 Test fixes 2024-04-20 22:33:41 +10:00
Kusal Kithul-Godage 85cf09b7d3 WW-5353 Stronger security defaults for 7.0 2024-04-20 22:10:52 +10:00
Kusal Kithul-Godage d0204f3152 Merge remote-tracking branch 'origin/master' into merge-master-to-70-2024-04-20 2024-04-20 22:09:50 +10:00
Kusal Kithul-Godage 3c21e82290 Merge pull request #916 from apache/WW-5418-struts-sec
WW-5418 Forbid Enums and Jasper classes
2024-04-20 20:01:06 +10:00
Lukasz Lenart 2352c68631 [maven-release-plugin] prepare for next development iteration 2024-04-20 09:24:32 +02:00
Kusal Kithul-Godage f5cfb88f1f Merge pull request #915 from atlassian-forks/issue/WW-5417-bump-ongl-version-to-fix-security-issue
WW-5417 bump ognl version to fix security issue
2024-04-19 17:47:40 +10:00
Kusal Kithul-Godage 63267a8e06 Merge pull request #914 from atlassian-forks/issue/WW-5409-introduce-final-attribute-to-package-element
WW-5409 introduce final attribute to package element which make them unextendable
2024-04-19 17:47:02 +10:00
Jeffery Xie 62b4b65c4c WW-5417 update ognl.version as 3.3.5 2024-04-19 14:58:25 +10:00
Kusal Kithul-Godage 4310536799 Merge pull request #917 from apache/WW-5406-injection-order 2024-04-19 08:37:14 +10:00
Kusal Kithul-Godage ad49ea866a WW-5406 Fix injection order issue for excluded patterns 2024-04-18 22:28:30 +10:00
Kusal Kithul-Godage 7e9f6e84f2 WW-5418 Exclude Tomcat Jasper classes 2024-04-18 11:14:10 +10:00
Kusal Kithul-Godage 100f5052d4 WW-5418 Forbid enums 2024-04-18 11:13:56 +10:00
Jeffery Xie 0a720971c7 WW-5417 bump ognl version to fix security issue 2024-04-18 11:02:16 +10:00
Erica S. Kane 6ac8b04ad2 WW-5400 Added @since Struts 6.5.0 to new properties as requested 2024-04-12 13:40:53 -04:00
Kusal Kithul-Godage 1562e66a89 Merge pull request #912 from atlassian-forks/issue/WW-5408-add-option-to-not-fallback-to-empty-namespace-when-unresolved
WW-5408 add option to not fallback to empty namespace when unresolved
2024-04-12 17:58:06 +10:00
Jeffery Xie 85783a0cc7 WW-5409 rename 6.5.0.dtd to 6.5.dtd to follow the naming pattern 2024-04-12 16:04:34 +10:00
Erica S. Kane 0151bdeb9c WW-5400 Better toString formatting 2024-04-11 16:52:44 -04:00
Kusal Kithul-Godage 0aa2f269f2 Merge pull request #911 from atlassian-forks/issue/WW-5407-extend-SecurityMemberAccess-proxy-detection-to-proxies
WW-5407 Extend SecurityMemberAccess proxy detection to other proxies
2024-04-11 19:16:36 +10:00
Jeffery Xie 4088f2ee2b WW-5409 update new dtd from 6.4.0 to 6.5.0 2024-04-11 15:07:25 +10:00
Jeffery Xie 1d51d00ecb WW-5408 add struts.actionConfig.fallbackToEmptyNamespace as true in default.properties 2024-04-11 14:57:57 +10:00
Kusal Kithul-Godage 929a60184b Merge pull request #910 from apache/WW-5406-excluded-patterns
WW-5406 Ensure Action excluded patterns are reinjected
2024-04-11 14:27:31 +10:00
Jeffery Xie bf5f29132b WW-5409 introduce final attribute to package element which make them unextendable 2024-04-11 12:44:53 +10:00
Erica S. Kane 758174c52e WW-5400 Extend default configuration options for the CSP interceptor.
Previously, it was impossible to set global options for the CSP interceptor. The only options was to have every action individually implement CspSettingsAware.

To fix this, we add an interceptor parameter of defaultCspSettingsClassName. Values from this class will be used in the CSP header instead of DefaultCspSettings. Users may define their own custom class which implements CspSettings, and that will be the default for all actions that do not implement the CspSettingsAware interface. It is now possible to create this custom class by simply extending DefaultCspSettings.

I have fixed a spelling error in DefaultCspSettings.java -- cratePolicyFormat renamed to createPolicyFormat.
2024-04-10 18:42:09 -04:00
Jeffery Xie f9f6327570 /WW-5408 rename struts.disableActionConfigFallbackToEmptyNamespace to struts.actionConfig.fallbackToEmptyNamespace 2024-04-10 17:30:02 +10:00
Kusal Kithul-Godage ed0c7287d9 WW-5406 Revert breaking API change 2024-04-10 16:01:47 +10:00
Kusal Kithul-Godage 1a6e2fef7a WW-5406 Add deprecation JavaDocs 2024-04-10 15:55:02 +10:00
Jeffery Xie 5f717cdb89 WW-5407 tweak ProxyUtil#isHibernateProxyMember to make it neat 2024-04-10 15:00:50 +10:00
Jeffery Xie e2ec114577 WW-5408 add option to not fallback to empty namespace when unresolved 2024-04-09 15:33:10 +10:00
Jeffery Xie 0074b70286 WW-5407 extend SecurityMemberAccess proxy detection to other proxies 2024-04-09 14:46:45 +10:00
Kusal Kithul-Godage efa20426c9 WW-5406 Ensure Action excluded patterns are reinjected 2024-04-09 10:42:12 +10:00
dependabot[bot] 268ed789ee Bump org.assertj:assertj-core from 3.25.2 to 3.25.3
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.25.2 to 3.25.3.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.25.2...assertj-build-3.25.3)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 01:22:47 +00:00
dependabot[bot] ccd5f5d534 Bump asm.version from 9.6 to 9.7
Bumps `asm.version` from 9.6 to 9.7.

Updates `org.ow2.asm:asm` from 9.6 to 9.7

Updates `org.ow2.asm:asm-commons` from 9.6 to 9.7

---
updated-dependencies:
- dependency-name: org.ow2.asm:asm
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.ow2.asm:asm-commons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 01:22:36 +00:00
dependabot[bot] ac409d6cba Bump org.apache.maven.plugins:maven-failsafe-plugin
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M6 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-M6...surefire-3.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 01:20:48 +00:00
Lukasz Lenart c6d13f1016 [maven-release-plugin] prepare for next development iteration 2024-04-07 11:22:16 +02:00
Lukasz Lenart 9c5c80d173 [maven-release-plugin] prepare release STRUTS_6_4_0 2024-04-07 11:22:06 +02:00
Lukasz Lenart a6d6d918fa Reverts release and fixes issue with assembly 2024-04-07 11:14:44 +02:00
Lukasz Lenart 5e8b81b382 [maven-release-plugin] prepare for next development iteration 2024-04-07 08:13:17 +02:00
Lukasz Lenart f8381fbdbf [maven-release-plugin] prepare release STRUTS_6_4_0 2024-04-07 08:13:07 +02:00
138 changed files with 2628 additions and 1724 deletions
+3 -2
View File
@@ -18,8 +18,9 @@ github:
contexts:
- build
required_pull_request_reviews:
require_code_owner_reviews: true
required_approving_review_count: 1
# it does not work because our github teams are private/secret, see INFRA-25666
require_code_owner_reviews: false
required_approving_review_count: 0
autolink_jira:
- WW
dependabot_alerts: true
+2 -2
View File
@@ -46,7 +46,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # 2.3.1
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # 2.3.3
with:
results_file: results.sarif
results_format: sarif
@@ -58,7 +58,7 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # 4.3.1
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # 4.3.4
with:
name: SARIF file
path: results.sarif
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</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-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
+2 -8
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -166,11 +166,6 @@
<configuration>
<outputDirectory>${project.build.directory}/extraclasspath</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-freemarker-jakarta</artifactId>
<destFileName>struts2-freemarker-jakarta.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-sitemesh2-jakarta</artifactId>
@@ -197,7 +192,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.0.0-M6</version>
<version>3.3.0</version>
<configuration>
<includes>
<include>it.org.apache.struts2.showcase.*Test</include>
@@ -241,7 +236,6 @@
<webAppSourceDirectory>${basedir}/src/main/webapp/</webAppSourceDirectory>
<webApp>
<extraClasspath>
${project.build.directory}/extraclasspath/struts2-freemarker-jakarta.jar,
${project.build.directory}/extraclasspath/struts2-sitemesh2-jakarta.jar,
${project.build.directory}/extraclasspath/struts2-velocity-tools-view-jakarta.jar,
${project.build.directory}/extraclasspath/struts2-velocity-tools-jsp-jakarta
+3 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-assembly</artifactId>
@@ -104,7 +104,9 @@
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<executions>
<execution>
<id>make-assembly</id>
+4 -4
View File
@@ -25,11 +25,11 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-bom</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -44,7 +44,7 @@
</licenses>
<properties>
<struts-version.version>7.0.0-M6</struts-version.version>
<struts-version.version>7.0.0-M8</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_M6</tag>
<tag>STRUTS_7_0_0_M8</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>
+18 -3
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -136,8 +136,8 @@
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-freemarker-jakarta</artifactId>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
@@ -185,6 +185,15 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<!-- Optional used in com.opensymphony.xwork2.util.ProxyUtil to detect if object is HibernateProxy -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.15.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -211,6 +220,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -90,6 +90,11 @@ public class ActionSupport implements Action, Validateable, ValidationAware, Tex
return getLocaleProvider().isValidLocale(locale);
}
@Override
public Locale toLocale(String localeStr) {
return getLocaleProvider().toLocale(localeStr);
}
@Override
public boolean hasKey(String key) {
return getTextProvider().hasKey(key);
@@ -46,17 +46,23 @@ public class DefaultLocaleProvider implements LocaleProvider {
@Override
public boolean isValidLocaleString(String localeStr) {
Locale locale = this.toLocale(localeStr);
return isValidLocale(locale);
}
@Override
public boolean isValidLocale(Locale locale) {
return locale != null && LocaleUtils.isAvailableLocale(locale);
}
@Override
public Locale toLocale(String localeStr) {
Locale locale = null;
try {
locale = LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
} catch (IllegalArgumentException e) {
LOG.warn(new ParameterizedMessage("Cannot convert [{}] to proper locale", localeStr), e);
}
return isValidLocale(locale);
}
@Override
public boolean isValidLocale(Locale locale) {
return LocaleUtils.isAvailableLocale(locale);
return locale;
}
}
@@ -18,6 +18,9 @@
*/
package com.opensymphony.xwork2;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Locale;
@@ -58,4 +61,17 @@ public interface LocaleProvider {
*/
boolean isValidLocale(Locale locale);
/**
* Tries to convert provided locale string into {@link Locale} or returns null
* @param localeStr a String representing locale, e.g.: en_EN
* @return instance of {@link Locale} or null
* @since Struts 6.5.0
*/
default Locale toLocale(String localeStr) {
try {
return LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
} catch (IllegalArgumentException e) {
return null;
}
}
}
@@ -36,6 +36,8 @@ import org.apache.commons.lang3.ClassUtils;
import java.util.Locale;
import java.util.Map;
import static java.util.Collections.singletonMap;
/**
* Base JUnit TestCase to extend for XWork specific JUnit tests. Uses
* the generic test setup for logic.
@@ -56,9 +58,7 @@ public abstract class XWorkTestCase extends TestCase {
@Override
protected void setUp() throws Exception {
configurationManager = XWorkTestCaseHelper.setUp();
configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
reloadConfiguration(configurationManager);
}
@Override
@@ -66,13 +66,17 @@ public abstract class XWorkTestCase extends TestCase {
XWorkTestCaseHelper.tearDown(configurationManager);
}
protected void loadConfigurationProviders(ConfigurationProvider... providers) {
configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
private void reloadConfiguration(ConfigurationManager configurationManager) {
configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
}
protected void loadConfigurationProviders(ConfigurationProvider... providers) {
configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
reloadConfiguration(configurationManager);
}
protected void loadButSet(Map<String, ?> properties) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override
@@ -115,4 +119,25 @@ public abstract class XWorkTestCase extends TestCase {
.getContextMap();
}
protected void setStrutsConstant(String constant, String value) {
setStrutsConstant(singletonMap(constant, value));
}
protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
configurationManager.addContainerProvider(new StubConfigurationProvider() {
@Override
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
}
}
@Override
public void destroy() {
}
});
configurationManager.reload();
reloadConfiguration(configurationManager);
}
}
@@ -47,6 +47,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
protected String name;
protected String namespace = "";
protected boolean isAbstract = false;
protected boolean isFinal = false; // a final package is unextendable
protected boolean needsRefresh;
protected boolean strictMethodInvocation = true;
@@ -69,6 +70,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
this.name = orig.name;
this.namespace = orig.namespace;
this.isAbstract = orig.isAbstract;
this.isFinal = orig.isFinal;
this.needsRefresh = orig.needsRefresh;
this.actionConfigs = new LinkedHashMap<>(orig.actionConfigs);
this.globalResultConfigs = new LinkedHashMap<>(orig.globalResultConfigs);
@@ -85,6 +87,10 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
return isAbstract;
}
public boolean isFinal() {
return isFinal;
}
public Map<String, ActionConfig> getActionConfigs() {
return actionConfigs;
}
@@ -360,6 +366,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
PackageConfig that = (PackageConfig) o;
if (isAbstract != that.isAbstract) return false;
if (isFinal != that.isFinal) return false;
if (needsRefresh != that.needsRefresh) return false;
if (strictMethodInvocation != that.strictMethodInvocation) return false;
if (actionConfigs != null ? !actionConfigs.equals(that.actionConfigs) : that.actionConfigs != null)
@@ -404,6 +411,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
result = 31 * result + name.hashCode();
result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
result = 31 * result + (isAbstract ? 1 : 0);
result = 31 * result + (isFinal ? 1 : 0);
result = 31 * result + (needsRefresh ? 1 : 0);
result = 31 * result + (strictMethodInvocation ? 1 : 0);
return result;
@@ -453,6 +461,11 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
return this;
}
public Builder isFinal(boolean isFinal) {
target.isFinal = isFinal;
return this;
}
public Builder defaultInterceptorRef(String name) {
target.defaultInterceptorRef = name;
return this;
@@ -120,6 +120,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -459,9 +460,12 @@ public class DefaultConfiguration implements Configuration {
boolean appendNamedParameters = Boolean.parseBoolean(
container.getInstance(String.class, StrutsConstants.STRUTS_MATCHER_APPEND_NAMED_PARAMETERS)
);
boolean fallbackToEmptyNamespace = Boolean.parseBoolean(
Optional.ofNullable(container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE)).orElse("true")
);
return new RuntimeConfigurationImpl(Collections.unmodifiableMap(namespaceActionConfigs),
Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters);
Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters, fallbackToEmptyNamespace);
}
private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig packageContext) {
@@ -536,14 +540,17 @@ public class DefaultConfiguration implements Configuration {
private final Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private final NamespaceMatcher namespaceMatcher;
private final Map<String, String> namespaceConfigs;
private final boolean fallbackToEmptyNamespace;
public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs,
Map<String, String> namespaceConfigs,
PatternMatcher<int[]> matcher,
boolean appendNamedParameters)
boolean appendNamedParameters,
boolean fallbackToEmptyNamespace)
{
this.namespaceActionConfigs = namespaceActionConfigs;
this.namespaceConfigs = namespaceConfigs;
this.fallbackToEmptyNamespace = fallbackToEmptyNamespace;
this.namespaceActionConfigMatchers = new LinkedHashMap<>();
this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet(), appendNamedParameters);
@@ -583,14 +590,17 @@ public class DefaultConfiguration implements Configuration {
}
// fail over to empty namespace
if (config == null && StringUtils.isNotBlank(namespace)) {
if (config == null && shouldFallbackToEmptyNamespace(namespace)) {
config = findActionConfigInNamespace("", name);
}
return config;
}
private boolean shouldFallbackToEmptyNamespace(String namespace) {
return StringUtils.isNotBlank(namespace) && ("/".equals(namespace) || fallbackToEmptyNamespace);
}
private ActionConfig findActionConfigInNamespace(String namespace, String name) {
ActionConfig config = null;
if (namespace == null) {
@@ -46,7 +46,7 @@ public class EnvsValueSubstitutor implements ValueSubstitutor {
public String substitute(String value) {
LOG.debug("Substituting value {} with proper System variable or environment variable", value);
String substituted = sysStrSubstitutor.replace(value);
return envStrSubstitutor.replace(substituted);
String substituted = envStrSubstitutor.replace(value);
return sysStrSubstitutor.replace(substituted);
}
}
@@ -82,14 +82,6 @@ public abstract class XmlConfigurationProvider extends XmlDocConfigurationProvid
this.configFileName = filename;
}
/**
* @deprecated since 6.2.0, use {@link #XmlConfigurationProvider(String)}
*/
@Deprecated
public XmlConfigurationProvider(String filename, @Deprecated boolean notUsed) {
this(filename);
}
@Override
public void init(Configuration configuration) {
super.init(configuration);
@@ -465,7 +465,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
Location location = DomHelper.getLocationObject(actionElement);
if (!className.isEmpty()) {
verifyAction(className, name, location);
verifyAction(className, location);
}
Map<String, ResultConfig> results;
@@ -496,7 +496,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
String methodName = trimToNull(actionElement.getAttribute("method"));
List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement);
Set<String> allowedMethods = buildAllowedMethods(actionElement, packageContext);
return new ActionConfig.Builder(packageContext.getName(), actionName, className)
@@ -511,15 +511,6 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
.build();
}
/**
* @deprecated since 6.2.0, use {@link #verifyAction(String, Location)}
*/
@Deprecated
protected boolean verifyAction(String className, String name, Location loc) {
verifyAction(className, loc);
return true;
}
protected void verifyAction(String className, Location loc) {
if (className.contains("{")) {
LOG.debug("Action class [{}] contains a wildcard replacement value, so it can't be verified", className);
@@ -603,8 +594,8 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
*/
protected PackageConfig.Builder buildPackageContext(Element packageElement) {
String parent = packageElement.getAttribute("extends");
String abstractVal = packageElement.getAttribute("abstract");
boolean isAbstract = parseBoolean(abstractVal);
boolean isAbstract = parseBoolean(packageElement.getAttribute("abstract"));
boolean isFinal = parseBoolean(packageElement.getAttribute("final"));
String name = defaultString(packageElement.getAttribute("name"));
String namespace = defaultString(packageElement.getAttribute("namespace"));
@@ -617,6 +608,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
PackageConfig.Builder cfg = new PackageConfig.Builder(name)
.namespace(namespace)
.isAbstract(isAbstract)
.isFinal(isFinal)
.strictMethodInvocation(strictDMI)
.location(DomHelper.getLocationObject(packageElement));
@@ -627,17 +619,23 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
// has parents, let's look it up
List<PackageConfig> parents = new ArrayList<>();
for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) {
if (configuration.getPackageConfigNames().contains(parentPackageName)) {
parents.add(configuration.getPackageConfig(parentPackageName));
} else if (declaredPackages.containsKey(parentPackageName)) {
if (configuration.getPackageConfig(parentPackageName) == null) {
addPackage(declaredPackages.get(parentPackageName));
boolean isParentPackageConfigDefined = false;
if (configuration.getPackageConfigNames().contains(parentPackageName)) { // parent package already added to configuration
isParentPackageConfigDefined = true;
} else if (declaredPackages.containsKey(parentPackageName)) { // parent package declared but yet added to configuration
addPackage(declaredPackages.get(parentPackageName));
isParentPackageConfigDefined = true;
}
if (isParentPackageConfigDefined) {
PackageConfig parentPackageConfig = configuration.getPackageConfig(parentPackageName);
if (parentPackageConfig.isFinal()) {
throw new ConfigurationException("Parent package is final and unextendable: " + parentPackageName);
}
parents.add(configuration.getPackageConfig(parentPackageName));
parents.add(parentPackageConfig);
} else {
throw new ConfigurationException("Parent package is not defined: " + parentPackageName);
}
}
if (parents.isEmpty()) {
@@ -778,14 +776,6 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
return sb.toString();
}
/**
* @deprecated since 6.2.0, use {@link #buildExceptionMappings(Element)}
*/
@Deprecated
protected List<ExceptionMappingConfig> buildExceptionMappings(Element element, PackageConfig.Builder packageContext) {
return buildExceptionMappings(element);
}
/**
* Build a list of exception mapping objects from below a given XML element.
*
@@ -923,7 +913,7 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
if (globalExceptionMappingList.getLength() > 0) {
Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(globalExceptionMappingElement);
packageContext.addGlobalExceptionMappingConfigs(exceptionMappings);
}
}
@@ -25,8 +25,6 @@ import java.util.Map;
/**
* ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept
* in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs.
*
* @author plightbo
*/
public interface ValidationAware {
@@ -119,7 +117,9 @@ public interface ValidationAware {
*
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
*/
boolean hasErrors();
default boolean hasErrors() {
return hasActionErrors() || hasFieldErrors();
}
/**
* Check whether there are any field errors associated with this action.
@@ -30,13 +30,6 @@ import org.apache.struts2.StrutsConstants;
public class DefaultOgnlBeanInfoCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements BeanInfoCacheFactory<Key, Value> {
/**
* @deprecated since 6.4.0, use {@link #DefaultOgnlBeanInfoCacheFactory(String, String)}
*/
@Deprecated
public DefaultOgnlBeanInfoCacheFactory() {
}
@Inject
public DefaultOgnlBeanInfoCacheFactory(@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE) String cacheMaxSize,
@Inject(value = StrutsConstants.STRUTS_OGNL_BEANINFO_CACHE_TYPE) String defaultCacheType) {
@@ -15,8 +15,6 @@
*/
package com.opensymphony.xwork2.ognl;
import org.apache.commons.lang3.BooleanUtils;
/**
* <p>Default OGNL Cache factory implementation.</p>
*
@@ -30,25 +28,23 @@ public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key
private static final int DEFAULT_INIT_CAPACITY = 16;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private CacheType defaultCacheType;
private int cacheMaxSize;
/**
* @deprecated since 6.4.0, use {@link #DefaultOgnlCacheFactory(int, CacheType)}
*/
@Deprecated
public DefaultOgnlCacheFactory() {
this(10000, CacheType.BASIC);
}
private final CacheType defaultCacheType;
private final int cacheMaxSize;
private final int initialCapacity;
public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType) {
this(cacheMaxSize, defaultCacheType, DEFAULT_INIT_CAPACITY);
}
public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType, int initialCapacity) {
this.cacheMaxSize = cacheMaxSize;
this.defaultCacheType = defaultCacheType;
this.initialCapacity = initialCapacity;
}
@Override
public OgnlCache<Key, Value> buildOgnlCache() {
return buildOgnlCache(getCacheMaxSize(), DEFAULT_INIT_CAPACITY, DEFAULT_LOAD_FACTOR, defaultCacheType);
return buildOgnlCache(getCacheMaxSize(), initialCapacity, DEFAULT_LOAD_FACTOR, defaultCacheType);
}
@Override
@@ -56,16 +52,11 @@ public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key
int initialCapacity,
float loadFactor,
CacheType cacheType) {
switch (cacheType) {
case BASIC:
return new OgnlDefaultCache<>(evictionLimit, initialCapacity, loadFactor);
case LRU:
return new OgnlLRUCache<>(evictionLimit, initialCapacity, loadFactor);
case WTLFU:
return new OgnlCaffeineCache<>(evictionLimit, initialCapacity);
default:
throw new IllegalArgumentException("Unknown cache type: " + cacheType);
}
return switch (cacheType) {
case BASIC -> new OgnlDefaultCache<>(evictionLimit, initialCapacity, loadFactor);
case LRU -> new OgnlLRUCache<>(evictionLimit, initialCapacity, loadFactor);
case WTLFU -> new OgnlCaffeineCache<>(evictionLimit, initialCapacity);
};
}
@Override
@@ -73,28 +64,8 @@ public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key
return cacheMaxSize;
}
/**
* @deprecated since 6.4.0
*/
@Deprecated
protected void setCacheMaxSize(String maxSize) {
cacheMaxSize = Integer.parseInt(maxSize);
}
@Override
public CacheType getDefaultCacheType() {
return defaultCacheType;
}
/**
* No effect when {@code useLRUMode} is {@code false}
*
* @deprecated since 6.4.0
*/
@Deprecated
protected void setUseLRUCache(String useLRUMode) {
if (BooleanUtils.toBoolean(useLRUMode)) {
defaultCacheType = CacheType.LRU;
}
}
}
@@ -30,13 +30,6 @@ import org.apache.struts2.StrutsConstants;
public class DefaultOgnlExpressionCacheFactory<Key, Value> extends DefaultOgnlCacheFactory<Key, Value>
implements ExpressionCacheFactory<Key, Value> {
/**
* @deprecated since 6.4.0, use {@link #DefaultOgnlExpressionCacheFactory(String, String)}
*/
@Deprecated
public DefaultOgnlExpressionCacheFactory() {
}
@Inject
public DefaultOgnlExpressionCacheFactory(@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE) String cacheMaxSize,
@Inject(value = StrutsConstants.STRUTS_OGNL_EXPRESSION_CACHE_TYPE) String defaultCacheType) {
@@ -33,7 +33,7 @@ public class ErrorMessageBuilder {
}
public ErrorMessageBuilder errorSettingExpressionWithValue(String expr, Object value) {
appenExpression(expr);
appendExpression(expr);
if (value instanceof Object[]) {
appendValueAsArray((Object[]) value, message);
} else {
@@ -42,7 +42,7 @@ public class ErrorMessageBuilder {
return this;
}
private void appenExpression(String expr) {
private void appendExpression(String expr) {
message.append("Error setting expression '");
message.append(expr);
message.append("' with value ");
@@ -25,22 +25,6 @@ package com.opensymphony.xwork2.ognl;
public interface OgnlCacheFactory<Key, Value> {
OgnlCache<Key, Value> buildOgnlCache();
/**
* Note that if {@code lruCache} is {@code false}, the cache type could still be LRU if the default cache type is
* configured as such.
* @deprecated since 6.4.0, use {@link #buildOgnlCache(int, int, float, CacheType)}
*/
@Deprecated
default OgnlCache<Key, Value> buildOgnlCache(int evictionLimit,
int initialCapacity,
float loadFactor,
boolean lruCache) {
return buildOgnlCache(evictionLimit,
initialCapacity,
loadFactor,
lruCache ? CacheType.LRU : getDefaultCacheType());
}
/**
* @param evictionLimit maximum capacity of the cache where applicable for cache type chosen
* @param initialCapacity initial capacity of the cache where applicable for cache type chosen
@@ -52,14 +36,6 @@ public interface OgnlCacheFactory<Key, Value> {
int getCacheMaxSize();
/**
* @deprecated since 6.4.0
*/
@Deprecated
default boolean getUseLRUCache() {
return CacheType.LRU.equals(getDefaultCacheType());
}
CacheType getDefaultCacheType();
enum CacheType {
@@ -36,7 +36,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.ognl.OgnlGuard;
import org.apache.struts2.ognl.StrutsOgnlGuard;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
@@ -46,14 +45,7 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import static com.opensymphony.xwork2.util.ConfigParseUtil.toClassesSet;
import static com.opensymphony.xwork2.util.ConfigParseUtil.toNewPatternsSet;
import static com.opensymphony.xwork2.util.ConfigParseUtil.toPackageNamesSet;
import static java.util.Collections.emptySet;
import static java.util.Objects.requireNonNull;
import static org.apache.struts2.ognl.OgnlGuard.EXPR_BLOCKED;
@@ -68,9 +60,6 @@ public class OgnlUtil {
private static final Logger LOG = LogManager.getLogger(OgnlUtil.class);
// Flag used to reduce flooding logs with WARNs about using DevMode excluded packages
private final AtomicBoolean warnReported = new AtomicBoolean(false);
private final OgnlCache<String, Object> expressionCache;
private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
private TypeConverter defaultConverter;
@@ -80,25 +69,8 @@ public class OgnlUtil {
private boolean enableExpressionCache = true;
private boolean enableEvalExpression;
private String devModeExcludedClasses = "";
private String devModeExcludedPackageNamePatterns = "";
private String devModeExcludedPackageNames = "";
private String devModeExcludedPackageExemptClasses = "";
private Container container;
/**
* Construct a new OgnlUtil instance for use with the framework
*
* @deprecated since 6.0.0. Use {@link #OgnlUtil(ExpressionCacheFactory, BeanInfoCacheFactory, OgnlGuard) instead.
*/
@Deprecated
public OgnlUtil() {
this(new DefaultOgnlExpressionCacheFactory<>(),
new DefaultOgnlBeanInfoCacheFactory<>(),
new StrutsOgnlGuard());
}
/**
* Construct a new OgnlUtil instance for use with the framework, with optional cache factories for OGNL Expression
* and BeanInfo caches.
@@ -156,119 +128,11 @@ public class OgnlUtil {
}
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setExcludedClasses(String commaDelimitedClasses) {
// Must be set directly on SecurityMemberAccess
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
protected void setDevModeExcludedClasses(String commaDelimitedClasses) {
this.devModeExcludedClasses = commaDelimitedClasses;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
// Must be set directly on SecurityMemberAccess
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
protected void setDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
this.devModeExcludedPackageNamePatterns = commaDelimitedPackagePatterns;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setExcludedPackageNames(String commaDelimitedPackageNames) {
// Must be set directly on SecurityMemberAccess
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
protected void setDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
this.devModeExcludedPackageNames = commaDelimitedPackageNames;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public void setExcludedPackageExemptClasses(String commaDelimitedClasses) {
// Must be set directly on SecurityMemberAccess
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
public void setDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
this.devModeExcludedPackageExemptClasses = commaDelimitedClasses;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public Set<String> getExcludedClasses() {
return toClassesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_CLASSES));
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public Set<Pattern> getExcludedPackageNamePatterns() {
return toNewPatternsSet(emptySet(), container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS));
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public Set<String> getExcludedPackageNames() {
return toPackageNamesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAMES));
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public Set<String> getExcludedPackageExemptClasses() {
return toClassesSet(container.getInstance(String.class, StrutsConstants.STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES));
}
@Inject
protected void setContainer(Container container) {
this.container = container;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setAllowStaticFieldAccess(String allowStaticFieldAccess) {
// Must be set directly on SecurityMemberAccess
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
// Must be set directly on SecurityMemberAccess
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) {
// Must be set directly on SecurityMemberAccess
}
/**
* @param maxLength Injects the Struts OGNL expression maximum length.
*/
@@ -288,22 +152,6 @@ public class OgnlUtil {
}
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public boolean isDisallowProxyMemberAccess() {
return BooleanUtils.toBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS));
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
public boolean isDisallowDefaultPackageAccess() {
return BooleanUtils.toBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS));
}
/**
* Convenience mechanism to clear the OGNL Runtime Cache via OgnlUtil. May be utilized
* by applications that generate many unique OGNL expressions over time.
@@ -856,6 +704,11 @@ public class OgnlUtil {
return createDefaultContext(root, null);
}
/**
* Note that the allowlist capability is not enforced by the {@link OgnlContext} returned by this method. Currently,
* this context is only leveraged by some public methods on {@link OgnlUtil} which are called by
* {@link OgnlReflectionProvider}.
*/
protected Map<String, Object> createDefaultContext(Object root, ClassResolver resolver) {
if (resolver == null) {
resolver = container.getInstance(RootAccessor.class);
@@ -867,17 +720,6 @@ public class OgnlUtil {
SecurityMemberAccess memberAccess = container.getInstance(SecurityMemberAccess.class);
memberAccess.useEnforceAllowlistEnabled(Boolean.FALSE.toString());
if (devMode) {
if (!warnReported.get()) {
warnReported.set(true);
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
}
memberAccess.useExcludedClasses(devModeExcludedClasses);
memberAccess.useExcludedPackageNamePatterns(devModeExcludedPackageNamePatterns);
memberAccess.useExcludedPackageNames(devModeExcludedPackageNames);
memberAccess.useExcludedPackageExemptClasses(devModeExcludedPackageExemptClasses);
}
return Ognl.createDefaultContext(root, memberAccess, resolver, defaultConverter);
}
@@ -23,7 +23,6 @@ import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.ClearableValueStack;
import com.opensymphony.xwork2.util.CompoundRoot;
@@ -109,34 +108,6 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
this(vs, xworkConverter, accessor, null, securityMemberAccess);
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, RootAccessor, TextProvider, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(ValueStack vs,
XWorkConverter xworkConverter,
CompoundRootAccessor accessor,
TextProvider prov,
boolean allowStaticFieldAccess) {
this(vs, xworkConverter, accessor, prov, new SecurityMemberAccess(allowStaticFieldAccess));
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(XWorkConverter, RootAccessor, TextProvider, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticFieldAccess) {
this(xworkConverter, accessor, prov, new SecurityMemberAccess(allowStaticFieldAccess));
}
/**
* @deprecated since 6.4.0, use {@link #OgnlValueStack(ValueStack, XWorkConverter, RootAccessor, SecurityMemberAccess)} instead.
*/
@Deprecated
protected OgnlValueStack(ValueStack vs, XWorkConverter xworkConverter, CompoundRootAccessor accessor, boolean allowStaticFieldAccess) {
this(vs, xworkConverter, accessor, new SecurityMemberAccess(allowStaticFieldAccess));
}
@Inject
protected void setOgnlUtil(OgnlUtil ognlUtil) {
this.ognlUtil = ognlUtil;
@@ -155,14 +126,6 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
((OgnlContext) context).setKeepLastEvaluation(false);
}
/**
* @deprecated since 6.4.0, use {@link #setRoot(XWorkConverter, RootAccessor, CompoundRoot, SecurityMemberAccess)} instead.
*/
@Deprecated
protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticFieldAccess) {
setRoot(xworkConverter, accessor, compoundRoot, new SecurityMemberAccess(allowStaticFieldAccess));
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
protected void setDevMode(String mode) {
this.devMode = BooleanUtils.toBoolean(mode);
@@ -535,12 +498,4 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
public void useExcludeProperties(Set<Pattern> excludeProperties) {
securityMemberAccess.useExcludeProperties(excludeProperties);
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected void setXWorkConverter(final XWorkConverter converter) {
// no-op
}
}
@@ -30,7 +30,6 @@ import com.opensymphony.xwork2.util.ValueStackFactory;
import ognl.MethodAccessor;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
@@ -159,12 +158,4 @@ public class OgnlValueStackFactory implements ValueStackFactory {
LOG.debug("Registered custom OGNL PropertyAccessor [{}] for class [{}]", propertyAccessor.getClass().getName(), cls.getName());
}
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated
protected boolean containerAllowsStaticFieldAccess() {
return BooleanUtils.toBoolean(container.getInstance(String.class, StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS));
}
}
@@ -29,9 +29,9 @@ import org.apache.struts2.ognl.ProviderAllowlist;
import org.apache.struts2.ognl.ThreadAllowlist;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
@@ -51,6 +51,8 @@ import static java.text.MessageFormat.format;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableSet;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
/**
* Allows access decisions to be made on the basis of whether a member is static or not.
@@ -77,16 +79,29 @@ 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();
private Set<String> excludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
private Set<Pattern> excludedPackageNamePatterns = emptySet();
private Set<String> excludedPackageNames = emptySet();
private Set<String> excludedPackageExemptClasses = emptySet();
private volatile boolean isDevModeInit;
private boolean isDevMode;
private Set<String> devModeExcludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
private Set<Pattern> devModeExcludedPackageNamePatterns = emptySet();
private Set<String> devModeExcludedPackageNames = emptySet();
private Set<String> devModeExcludedPackageExemptClasses = emptySet();
private boolean enforceAllowlistEnabled = false;
private Set<Class<?>> allowlistClasses = emptySet();
private Set<String> allowlistPackageNames = emptySet();
private boolean disallowProxyObjectAccess = false;
private boolean disallowProxyMemberAccess = false;
private boolean disallowDefaultPackageAccess = false;
@@ -147,11 +162,11 @@ public class SecurityMemberAccess implements MemberAccess {
if (target != null) {
// Special case: Target is a Class object but not Class.class
if (Class.class.equals(target.getClass()) && !Class.class.equals(target)) {
if (!isStatic(member)) {
throw new IllegalArgumentException("Member expected to be static!");
if (!isStatic(member) && !Constructor.class.equals(member.getClass())) {
throw new IllegalArgumentException("Member expected to be static or constructor!");
}
if (!member.getDeclaringClass().equals(target)) {
throw new IllegalArgumentException("Target class does not match static member!");
throw new IllegalArgumentException("Target class does not match member!");
}
target = null; // This information is not useful to us and conflicts with following logic which expects target to be null or an instance containing the member
// Standard case: Member should exist on target
@@ -160,6 +175,11 @@ public class SecurityMemberAccess implements MemberAccess {
}
}
if (!checkProxyObjectAccess(target)) {
LOG.warn("Access to proxy is blocked! Target [{}], proxy class [{}]", target, target.getClass().getName());
return false;
}
if (!checkProxyMemberAccess(target, member)) {
LOG.warn("Access to proxy is blocked! Member class [{}] of target [{}], member [{}]", member.getDeclaringClass(), target, member);
return false;
@@ -203,12 +223,27 @@ public class SecurityMemberAccess implements MemberAccess {
* @return {@code true} if member access is allowed
*/
protected boolean checkAllowlist(Object target, Member member) {
Class<?> memberClass = member.getDeclaringClass();
if (!enforceAllowlistEnabled) {
return true;
}
if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
// If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
// classes/members. This allows the allowlist capability to continue working and offer some level of
// protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
// entities. This is preferred to having to disable the allowlist capability entirely.
Object newTarget = ProxyUtil.getHibernateProxyTarget(target);
if (newTarget != target) {
logAllowlistHibernateEntity(target, newTarget);
target = newTarget;
member = ProxyUtil.resolveTargetMember(member, newTarget);
}
}
Class<?> memberClass = member.getDeclaringClass();
if (!isClassAllowlisted(memberClass)) {
LOG.warn(format("Declaring class [{0}] of member type [{1}] is not allowlisted!", memberClass, member));
LOG.warn("Declaring class [{}] of member type [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
memberClass, member, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
return false;
}
if (target == null || target.getClass() == memberClass) {
@@ -216,12 +251,27 @@ public class SecurityMemberAccess implements MemberAccess {
}
Class<?> targetClass = target.getClass();
if (!isClassAllowlisted(targetClass)) {
LOG.warn(format("Target class [{0}] of target [{1}] is not allowlisted!", targetClass, target));
LOG.warn("Target class [{}] of target [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
targetClass, target, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
return false;
}
return true;
}
private void logAllowlistHibernateEntity(Object original, Object resolved) {
if (!isDevMode && !LOG.isDebugEnabled()) {
return;
}
String msg = "Hibernate entity [{}] resolved to [{}] for purpose of OGNL allowlisting." +
" We don't recommend executing OGNL expressions against Hibernate entities, you may disallow this behaviour using the configuration `{}=true`.";
Object[] args = {original, resolved, StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS};
if (isDevMode) {
LOG.warn(msg, args);
} else {
LOG.debug(msg, args);
}
}
protected boolean isClassAllowlisted(Class<?> clazz) {
return allowlistClasses.contains(clazz)
|| ALLOWLIST_REQUIRED_CLASSES.contains(clazz)
@@ -235,6 +285,7 @@ public class SecurityMemberAccess implements MemberAccess {
* @return {@code true} if member access is allowed
*/
protected boolean checkExclusionList(Object target, Member member) {
useDevModeConfiguration();
Class<?> memberClass = member.getDeclaringClass();
if (isClassExcluded(memberClass)) {
LOG.warn("Declaring class of member type [{}] is excluded!", memberClass);
@@ -286,7 +337,14 @@ public class SecurityMemberAccess implements MemberAccess {
}
/**
* @return {@code true} if member access is allowed
* @return {@code true} if proxy object access is allowed
*/
protected boolean checkProxyObjectAccess(Object target) {
return !(disallowProxyObjectAccess && ProxyUtil.isProxy(target));
}
/**
* @return {@code true} if proxy member access is allowed
*/
protected boolean checkProxyMemberAccess(Object target, Member member) {
return !(disallowProxyMemberAccess && ProxyUtil.isProxyMember(member, target));
@@ -300,10 +358,6 @@ public class SecurityMemberAccess implements MemberAccess {
* @return {@code true} if member access is allowed
*/
protected boolean checkStaticMethodAccess(Member member) {
if (checkEnumAccess(member)) {
LOG.trace("Exempting Enum#values from static method check: class [{}]", member.getDeclaringClass());
return true;
}
return member instanceof Field || !isStatic(member);
}
@@ -334,17 +388,6 @@ public class SecurityMemberAccess implements MemberAccess {
return Modifier.isPublic(member.getModifiers());
}
/**
* @return {@code true} if member access is allowed
*/
protected boolean checkEnumAccess(Member member) {
return member.getDeclaringClass().isEnum()
&& isStatic(member)
&& member instanceof Method
&& member.getName().equals("values")
&& ((Method) member).getParameterCount() == 0;
}
protected boolean isPackageExcluded(Class<?> clazz) {
return !excludedPackageExemptClasses.contains(clazz.getName()) && (isExcludedPackageNames(clazz) || isExcludedPackageNamePatterns(clazz));
}
@@ -438,16 +481,21 @@ public class SecurityMemberAccess implements MemberAccess {
this.enforceAllowlistEnabled = BooleanUtils.toBoolean(enforceAllowlistEnabled);
}
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_CLASSES, required = false)
@Inject(value = STRUTS_ALLOWLIST_CLASSES, required = false)
public void useAllowlistClasses(String commaDelimitedClasses) {
this.allowlistClasses = toClassObjectsSet(commaDelimitedClasses);
}
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
@Inject(value = STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
public void useAllowlistPackageNames(String commaDelimitedPackageNames) {
this.allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames);
}
@Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, required = false)
public void useDisallowProxyObjectAccess(String disallowProxyObjectAccess) {
this.disallowProxyObjectAccess = BooleanUtils.toBoolean(disallowProxyObjectAccess);
}
@Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, required = false)
public void useDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
this.disallowProxyMemberAccess = BooleanUtils.toBoolean(disallowProxyMemberAccess);
@@ -457,4 +505,41 @@ public class SecurityMemberAccess implements MemberAccess {
public void useDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) {
this.disallowDefaultPackageAccess = BooleanUtils.toBoolean(disallowDefaultPackageAccess);
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
protected void useDevMode(String devMode) {
this.isDevMode = BooleanUtils.toBoolean(devMode);
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
public void useDevModeExcludedClasses(String commaDelimitedClasses) {
this.devModeExcludedClasses = toNewClassesSet(devModeExcludedClasses, commaDelimitedClasses);
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
public void useDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
this.devModeExcludedPackageNamePatterns = toNewPatternsSet(devModeExcludedPackageNamePatterns, commaDelimitedPackagePatterns);
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
public void useDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
this.devModeExcludedPackageNames = toNewPackageNamesSet(devModeExcludedPackageNames, commaDelimitedPackageNames);
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
public void useDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
this.devModeExcludedPackageExemptClasses = toClassesSet(commaDelimitedClasses);
}
private void useDevModeConfiguration() {
if (!isDevMode || isDevModeInit) {
return;
}
isDevModeInit = true;
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
excludedClasses = devModeExcludedClasses;
excludedPackageNamePatterns = devModeExcludedPackageNamePatterns;
excludedPackageNames = devModeExcludedPackageNames;
excludedPackageExemptClasses = devModeExcludedPackageExemptClasses;
}
}
@@ -38,8 +38,12 @@ import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
@@ -76,6 +80,8 @@ public class CompoundRootAccessor implements RootAccessor {
private static final Map<MethodCall, Boolean> invalidMethods = new ConcurrentHashMap<>();
private boolean devMode;
private boolean disallowCustomOgnlMap;
private static final Set<String> ALLOWED_MAP_CLASSES = Set.of(
HashMap.class.getName(), TreeMap.class.getName(), LinkedHashMap.class.getName());
@Inject(StrutsConstants.STRUTS_DEVMODE)
protected void setDevMode(String mode) {
@@ -286,7 +292,7 @@ public class CompoundRootAccessor implements RootAccessor {
if (disallowCustomOgnlMap) {
String nodeClassName = ((OgnlContext) context).getCurrentNode().getClass().getName();
if ("ognl.ASTMap".equals(nodeClassName)) {
if ("ognl.ASTMap".equals(nodeClassName) && !ALLOWED_MAP_CLASSES.contains(className)) {
LOG.error("Constructing OGNL ASTMap's from custom classes is forbidden. Attempted class: {}", className);
return null;
}
@@ -16,26 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
package com.opensymphony.xwork2.util;
import org.apache.struts2.dispatcher.RequestMap;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import org.apache.logging.log4j.Logger;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* @since 6.5.0
*/
public final class DebugUtils {
@Deprecated
public interface RequestAware extends ServletRequestAware {
@Override
default void setServletRequest(HttpServletRequest httpServletRequest) {
// default no-op
public static void notifyDeveloperOfError(Logger log, Object action, String message) {
if (action instanceof TextProvider) {
TextProvider tp = (TextProvider) action;
message = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{message});
}
log.error(message);
if (action instanceof ValidationAware) {
ValidationAware validationAware = (ValidationAware) action;
validationAware.addActionError(message);
}
}
@Override
default void withServletRequest(HttpServletRequest request) {
ServletRequestAware.super.withServletRequest(request);
setRequest(new RequestMap(request));
}
void setRequest(Map<String, Object> request);
}
@@ -18,13 +18,23 @@
*/
package com.opensymphony.xwork2.util;
import com.opensymphony.xwork2.ognl.DefaultOgnlCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlCache;
import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import java.lang.reflect.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import static java.lang.reflect.Modifier.isPublic;
/**
* <code>ProxyUtil</code>
@@ -38,11 +48,13 @@ public class ProxyUtil {
private static final String SPRING_SPRINGPROXY_CLASS_NAME = "org.springframework.aop.SpringProxy";
private static final String SPRING_SINGLETONTARGETSOURCE_CLASS_NAME = "org.springframework.aop.target.SingletonTargetSource";
private static final String SPRING_TARGETCLASSAWARE_CLASS_NAME = "org.springframework.aop.TargetClassAware";
private static final Map<Class<?>, Boolean> isProxyCache =
new ConcurrentHashMap<>(256);
private static final Map<Member, Boolean> isProxyMemberCache =
new ConcurrentHashMap<>(256);
private static final String HIBERNATE_HIBERNATEPROXY_CLASS_NAME = "org.hibernate.proxy.HibernateProxy";
private static final int CACHE_MAX_SIZE = 10000;
private static final int CACHE_INITIAL_CAPACITY = 256;
private static final OgnlCache<Class<?>, Boolean> isProxyCache = new DefaultOgnlCacheFactory<Class<?>, Boolean>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
private static final OgnlCache<Member, Boolean> isProxyMemberCache = new DefaultOgnlCacheFactory<Member, Boolean>(
CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
/**
* Determine the ultimate target class of the given instance, traversing
@@ -75,7 +87,7 @@ public class ProxyUtil {
return flag;
}
boolean isProxy = isSpringAopProxy(object);
boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
isProxyCache.put(clazz, isProxy);
return isProxy;
@@ -87,7 +99,7 @@ public class ProxyUtil {
* @param object the object to check
*/
public static boolean isProxyMember(Member member, Object object) {
if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object)) {
if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object) && !isHibernateProxy(object)) {
return false;
}
@@ -96,12 +108,40 @@ public class ProxyUtil {
return flag;
}
boolean isProxyMember = isSpringProxyMember(member);
boolean isProxyMember = isSpringProxyMember(member) || isHibernateProxyMember(member);
isProxyMemberCache.put(member, isProxyMember);
return isProxyMember;
}
/**
* Check whether the given object is a Hibernate proxy.
*
* @param object the object to check
*/
public static boolean isHibernateProxy(Object object) {
try {
return HibernateProxy.class.isAssignableFrom(object.getClass());
} catch (NoClassDefFoundError ignored) {
return false;
}
}
/**
* Check whether the given member is a member of a Hibernate proxy.
*
* @param member the member to check
*/
public static boolean isHibernateProxyMember(Member member) {
try {
Class<?> clazz = ClassLoaderUtil.loadClass(HIBERNATE_HIBERNATEPROXY_CLASS_NAME, ProxyUtil.class);
return hasMember(clazz, member);
} catch (ClassNotFoundException ignored) {
}
return false;
}
/**
* Determine the ultimate target class of the given spring bean instance, traversing
* not only a top-level spring proxy but any number of nested spring proxies as well &mdash;
@@ -218,4 +258,34 @@ public class ProxyUtil {
return false;
}
/**
* @return the target instance of the given object if it is a Hibernate proxy object, otherwise the given object
*/
public static Object getHibernateProxyTarget(Object object) {
try {
return Hibernate.unproxy(object);
} catch (NoClassDefFoundError ignored) {
return object;
}
}
/**
* @return matching member on target object if one exists, otherwise the same member
*/
public static Member resolveTargetMember(Member proxyMember, Object target) {
int mod = proxyMember.getModifiers();
if (proxyMember instanceof Method) {
if (isPublic(mod)) {
return MethodUtils.getMatchingAccessibleMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
} else {
return MethodUtils.getMatchingMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
}
} else if (proxyMember instanceof Field) {
return FieldUtils.getField(target.getClass(), proxyMember.getName(), isPublic(mod));
} else if (proxyMember instanceof Constructor && isPublic(mod)) {
return ConstructorUtils.getMatchingAccessibleConstructor(target.getClass(), ((Constructor<?>) proxyMember).getParameterTypes());
}
return proxyMember;
}
}
@@ -36,7 +36,7 @@ public interface ActionValidatorManager {
* @param method the name of the method being invoked on the action - can be <tt>null</tt>.
* @return a list of all validators for the given class and context.
*/
List<Validator> getValidators(Class clazz, String context, String method);
List<Validator> getValidators(Class<?> clazz, String context, String method);
/**
* Returns a list of validators for the given class and context. This is the primary
@@ -46,7 +46,7 @@ public interface ActionValidatorManager {
* @param context the context of the action class - can be <tt>null</tt>.
* @return a list of all validators for the given class and context.
*/
List<Validator> getValidators(Class clazz, String context);
List<Validator> getValidators(Class<?> clazz, String context);
/**
* Validates the given object using action and its context.
@@ -125,7 +125,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
* @param context context
* @return a validator key which is the class name plus context.
*/
protected String buildValidatorKey(Class clazz, String context) {
protected String buildValidatorKey(Class<?> clazz, String context) {
return clazz.getName() + "/" + context;
}
@@ -137,7 +137,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
}
@Override
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
public synchronized List<Validator> getValidators(Class<?> clazz, String context, String method) {
String validatorKey = buildValidatorKey(clazz, context);
if (!validatorCache.containsKey(validatorKey)) {
@@ -158,7 +158,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
}
@Override
public synchronized List<Validator> getValidators(Class clazz, String context) {
public synchronized List<Validator> getValidators(Class<?> clazz, String context) {
return getValidators(clazz, context, null);
}
@@ -277,7 +277,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
* @param checked the set of previously checked class-contexts, null if none have been checked
* @return a list of validator configs for the given class and context.
*/
protected List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
protected List<ValidatorConfig> buildValidatorConfigs(Class<?> clazz, String context, boolean checkFile, Set<String> checked) {
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
if (checked == null) {
@@ -287,7 +287,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
}
if (clazz.isInterface()) {
for (Class anInterface : clazz.getInterfaces()) {
for (Class<?> anInterface : clazz.getInterfaces()) {
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
}
} else {
@@ -297,7 +297,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
}
// look for validators for implemented interfaces
for (Class anInterface1 : clazz.getInterfaces()) {
for (Class<?> anInterface1 : clazz.getInterfaces()) {
if (checked.contains(anInterface1.getName())) {
continue;
}
@@ -317,17 +317,17 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
return validatorConfigs;
}
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class<?> aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
protected List<ValidatorConfig> buildClassValidatorConfigs(Class<?> aClass, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
protected List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
protected List<ValidatorConfig> loadFile(String fileName, Class<?> clazz, boolean checkFile) {
List<ValidatorConfig> retList = Collections.emptyList();
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
@@ -122,10 +122,15 @@ public class DelegatingValidatorContext implements ValidatorContext {
return localeProvider.isValidLocale(locale);
}
@Override
public Locale toLocale(String localeStr) {
return localeProvider.toLocale(localeStr);
}
public boolean hasKey(String key) {
return textProvider.hasKey(key);
}
public String getText(String aTextName) {
return textProvider.getText(aTextName);
}
@@ -280,6 +285,11 @@ public class DelegatingValidatorContext implements ValidatorContext {
public boolean isValidLocale(Locale locale) {
return getLocaleProvider().isValidLocale(locale);
}
@Override
public Locale toLocale(String localeStr) {
return getLocaleProvider().toLocale(localeStr);
}
}
/**
@@ -237,6 +237,8 @@ public final class StrutsConstants {
public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter";
public static final String STRUTS_ALWAYS_SELECT_FULL_NAMESPACE = "struts.mapper.alwaysSelectFullNamespace";
/** Fallback to empty namespace when request namespace didn't match any in action configuration */
public static final String STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE = "struts.actionConfig.fallbackToEmptyNamespace";
/** The {@link com.opensymphony.xwork2.LocaleProviderFactory} implementation class */
public static final String STRUTS_LOCALE_PROVIDER_FACTORY = "struts.localeProviderFactory";
@@ -308,13 +310,6 @@ public final class StrutsConstants {
*/
public static final String STRUTS_OGNL_BEANINFO_CACHE_MAXSIZE = "struts.ognl.beanInfoCacheMaxSize";
/**
* @since 6.0.0
* @deprecated since 6.4.0, use {@link StrutsConstants#STRUTS_OGNL_BEANINFO_CACHE_TYPE} instead.
*/
@Deprecated
public static final String STRUTS_OGNL_BEANINFO_CACHE_LRU_MODE = "struts.ognl.beanInfoCacheLRUMode";
/**
* Logs properties that are not found (very verbose)
* @since 6.0.0
@@ -370,13 +365,6 @@ public final class StrutsConstants {
*/
public static final String STRUTS_OGNL_EXPRESSION_CACHE_MAXSIZE = "struts.ognl.expressionCacheMaxSize";
/**
* @since 6.0.0
* @deprecated since 6.4.0, use {@link StrutsConstants#STRUTS_OGNL_EXPRESSION_CACHE_TYPE} instead.
*/
@Deprecated
public static final String STRUTS_OGNL_EXPRESSION_CACHE_LRU_MODE = "struts.ognl.expressionCacheLRUMode";
/**
* Enables evaluation of OGNL expressions
* @since 6.0.0
@@ -487,6 +475,7 @@ public final class StrutsConstants {
public static final String STRUTS_TEXT_PROVIDER_FACTORY = "struts.textProviderFactory";
public static final String STRUTS_LOCALIZED_TEXT_PROVIDER = "struts.localizedTextProvider";
public static final String STRUTS_DISALLOW_PROXY_OBJECT_ACCESS = "struts.disallowProxyObjectAccess";
public static final String STRUTS_DISALLOW_PROXY_MEMBER_ACCESS = "struts.disallowProxyMemberAccess";
public static final String STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS = "struts.disallowDefaultPackageAccess";
@@ -103,9 +103,9 @@ public class ServletUrlRenderer implements UrlRenderer {
}
result = urlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
}
String anchor = urlComponent.getAnchor();
if (StringUtils.isNotEmpty(anchor)) {
result += '#' + urlComponent.findString(anchor);
if (StringUtils.isNotEmpty(urlComponent.getAnchor())) {
String anchor = urlComponent.findString(urlComponent.getAnchor());
result += '#' + anchor;
}
if (urlComponent.isPutInContext()) {
@@ -292,7 +292,7 @@ public class ServletUrlRenderer implements UrlRenderer {
private void includeGetParameters(UrlProvider urlComponent) {
String query = extractQueryString(urlComponent);
QueryStringParser.Result result = queryStringParser.parse(query);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
result = mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
if (!result.getQueryFragment().isEmpty()) {
urlComponent.setAnchor(result.getQueryFragment());
}
@@ -331,10 +331,11 @@ public class ServletUrlRenderer implements UrlRenderer {
* @param value the value attribute (URL to be generated by this component)
* @param parameters component parameters
* @param contextParameters request parameters
* @return {@link QueryStringParser.Result} of value's ?query-string or empty()
*/
protected void mergeRequestParameters(String value, Map<String, Object> parameters, Map<String, ?> contextParameters) {
protected QueryStringParser.Result mergeRequestParameters(String value, Map<String, Object> parameters, Map<String, ?> contextParameters) {
Map<String, Object> mergedParams = new LinkedHashMap<>(contextParameters);
QueryStringParser.Result result = queryStringParser.empty();
// Merge contextParameters (from current request) with parameters specified in value attribute
// eg. value="someAction.action?id=someId&venue=someVenue"
@@ -343,7 +344,8 @@ public class ServletUrlRenderer implements UrlRenderer {
if (StringUtils.contains(value, "?")) {
String queryString = value.substring(value.indexOf('?') + 1);
mergedParams = new LinkedHashMap<>(queryStringParser.parse(queryString).getQueryParams());
result = queryStringParser.parse(queryString);
mergedParams = new LinkedHashMap<>(result.getQueryParams());
for (Map.Entry<String, ?> entry : contextParameters.entrySet()) {
if (!mergedParams.containsKey(entry.getKey())) {
mergedParams.put(entry.getKey(), entry.getValue());
@@ -362,6 +364,8 @@ public class ServletUrlRenderer implements UrlRenderer {
parameters.put(entry.getKey(), entry.getValue());
}
}
return result;
}
}
@@ -104,17 +104,17 @@ public class Set extends ContextBean {
body="";
if (DispatcherConstants.APPLICATION.equalsIgnoreCase(scope)) {
stack.setValue("#application['" + getVar() + "']", o);
stack.setValue(String.format("#application[\"%s\"]", getVar()), o);
} else if (DispatcherConstants.SESSION.equalsIgnoreCase(scope)) {
stack.setValue("#session['" + getVar() + "']", o);
stack.setValue(String.format("#session[\"%s\"]", getVar()), o);
} else if (DispatcherConstants.REQUEST.equalsIgnoreCase(scope)) {
stack.setValue("#request['" + getVar() + "']", o);
stack.setValue(String.format("#request[\"%s\"]", getVar()), o);
} else if (DispatcherConstants.PAGE.equalsIgnoreCase(scope)) {
stack.setValue("#attr['" + getVar() + "']", o, false);
stack.setValue(String.format("#attr[\"%s\"]", getVar()), o, false);
} else {
// Default scope is action. Note: The action scope handling also adds the var to the page scope.
stack.getContext().put(getVar(), o);
stack.setValue("#attr['" + getVar() + "']", o, false);
putInContext(o);
stack.setValue(String.format("#attr[\"%s\"]", getVar()), o, false);
}
return super.end(writer, body);
@@ -25,10 +25,10 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Context;
import com.opensymphony.xwork2.inject.Factory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import jakarta.servlet.ServletContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import jakarta.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -54,6 +54,7 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 6.5//EN", "struts-6.5.dtd");
}});
private File baseDir = null;
private final String filename;
@@ -104,14 +105,6 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
}
}
/**
* @deprecated since 6.2.0, use {@link #StrutsXmlConfigurationProvider(String, ServletContext)}
*/
@Deprecated
public StrutsXmlConfigurationProvider(String filename, @Deprecated boolean errorIfMissing, ServletContext ctx) {
this(filename, ctx);
}
/* (non-Javadoc)
* @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#register(com.opensymphony.xwork2.inject.ContainerBuilder, java.util.Properties)
*/
@@ -90,6 +90,7 @@ public class ConstantConfig {
private Boolean freemarkerWrapperAltMap;
private BeanConfig xworkConverter;
private Boolean mapperAlwaysSelectFullNamespace;
private Boolean actionConfigFallbackToEmptyNamespace;
private BeanConfig localeProviderFactory;
private String mapperIdParameterName;
private Boolean ognlAllowStaticFieldAccess;
@@ -145,6 +146,7 @@ public class ConstantConfig {
private String strictMethodInvocationMethodRegex;
private BeanConfig textProviderFactory;
private BeanConfig localizedTextProvider;
private Boolean disallowProxyObjectAccess;
private Boolean disallowProxyMemberAccess;
private Integer ognlAutoGrowthCollectionLimit;
private String staticContentPath;
@@ -225,6 +227,7 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP, Objects.toString(freemarkerWrapperAltMap, null));
map.put(StrutsConstants.STRUTS_XWORKCONVERTER, beanConfToString(xworkConverter));
map.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, Objects.toString(mapperAlwaysSelectFullNamespace, null));
map.put(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, Objects.toString(actionConfigFallbackToEmptyNamespace, null));
map.put(StrutsConstants.STRUTS_LOCALE_PROVIDER_FACTORY, beanConfToString(localeProviderFactory));
map.put(StrutsConstants.STRUTS_ID_PARAMETER_NAME, mapperIdParameterName);
map.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Objects.toString(ognlAllowStaticFieldAccess, null));
@@ -279,6 +282,7 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_SMI_METHOD_REGEX, strictMethodInvocationMethodRegex);
map.put(StrutsConstants.STRUTS_TEXT_PROVIDER_FACTORY, beanConfToString(textProviderFactory));
map.put(StrutsConstants.STRUTS_LOCALIZED_TEXT_PROVIDER, beanConfToString(localizedTextProvider));
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Objects.toString(disallowProxyObjectAccess, null));
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
@@ -812,6 +816,14 @@ public class ConstantConfig {
this.mapperAlwaysSelectFullNamespace = mapperAlwaysSelectFullNamespace;
}
public Boolean getActionConfigFallbackToEmptyNamespace() {
return actionConfigFallbackToEmptyNamespace;
}
public void setActionConfigFallbackToEmptyNamespace(Boolean actionConfigFallbackToEmptyNamespace) {
this.actionConfigFallbackToEmptyNamespace = actionConfigFallbackToEmptyNamespace;
}
public BeanConfig getLocaleProviderFactory() {
return localeProviderFactory;
}
@@ -1360,6 +1372,14 @@ public class ConstantConfig {
this.localizedTextProvider = new BeanConfig(clazz, clazz.getName());
}
public Boolean getDisallowProxyObjectAccess() {
return disallowProxyObjectAccess;
}
public void setDisallowProxyObjectAccess(Boolean disallowProxyObjectAccess) {
this.disallowProxyObjectAccess = disallowProxyObjectAccess;
}
public Boolean getDisallowProxyMemberAccess() {
return disallowProxyMemberAccess;
}
@@ -48,6 +48,10 @@ import com.opensymphony.xwork2.util.ValueStackFactory;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.location.Location;
import com.opensymphony.xwork2.util.location.LocationUtils;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
@@ -72,12 +76,9 @@ import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ObjectFactoryDestroyable;
import org.apache.struts2.util.fs.JBossFileManager;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -88,6 +89,10 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
/**
* A utility class the actual dispatcher delegates most of its tasks to. Each instance
* of the primary dispatcher holds an instance of this dispatcher to be shared for
@@ -162,6 +167,10 @@ public class Dispatcher {
*/
private Pattern multipartValidationPattern = Pattern.compile(MULTIPART_FORM_DATA_REGEX);
private String actionExcludedPatternsStr;
private String actionExcludedPatternsSeparator = ",";
private List<Pattern> actionExcludedPatterns;
/**
* Provide list of default configuration files.
*/
@@ -322,14 +331,6 @@ public class Dispatcher {
multipartSaveDir = val;
}
/**
* @deprecated since 6.4.0, no replacement.
*/
@Deprecated(since = "6.4.0", forRemoval = true)
public void setMultipartHandler(String val) {
// no-op
}
@Inject(value = StrutsConstants.STRUTS_MULTIPART_ENABLED, required = false)
public void setMultipartSupportEnabled(String multipartSupportEnabled) {
this.multipartSupportEnabled = Boolean.parseBoolean(multipartSupportEnabled);
@@ -340,6 +341,33 @@ public class Dispatcher {
this.multipartValidationPattern = Pattern.compile(multipartValidationRegex);
}
@Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, required = false)
public void setActionExcludedPatternsSeparator(String separator) {
this.actionExcludedPatternsSeparator = separator;
}
@Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, required = false)
public void setActionExcludedPatterns(String excludedPatterns) {
this.actionExcludedPatternsStr = excludedPatterns;
}
public List<Pattern> getActionExcludedPatterns() {
if (actionExcludedPatterns == null) {
initActionExcludedPatterns();
}
return actionExcludedPatterns;
}
private void initActionExcludedPatterns() {
if (actionExcludedPatternsStr == null || actionExcludedPatternsStr.trim().isEmpty()) {
actionExcludedPatterns = emptyList();
return;
}
actionExcludedPatterns = unmodifiableList(
Arrays.stream(actionExcludedPatternsStr.split(actionExcludedPatternsSeparator))
.map(String::trim).map(Pattern::compile).collect(toList()));
}
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
@@ -515,14 +543,6 @@ public class Dispatcher {
return new StrutsXmlConfigurationProvider(filename, ctx);
}
/**
* @deprecated since 6.2.0, use {@link #createStrutsXmlConfigurationProvider(String, ServletContext)}
*/
@Deprecated
protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return createStrutsXmlConfigurationProvider(filename, ctx);
}
private void init_JavaConfigurations() {
String configClasses = initParams.get("javaConfigClasses");
if (configClasses != null) {
@@ -19,15 +19,10 @@
package org.apache.struts2.dispatcher;
import com.opensymphony.xwork2.ActionContext;
import org.apache.struts2.StrutsConstants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Contains initialization operations
@@ -93,34 +88,4 @@ public class InitOperations {
public void cleanup() {
ActionContext.clear();
}
/**
* Extract a list of patterns to exclude from request filtering
*
* @param dispatcher The dispatcher to check for exclude pattern configuration
* @return a List of Patterns for request to exclude if apply, or <tt>null</tt>
* @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN
*/
public List<Pattern> buildExcludedPatternsList(Dispatcher dispatcher) {
String excludePatterns = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN);
String separator = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
if (separator == null) {
separator = ",";
}
return buildExcludedPatternsList(excludePatterns, separator);
}
private List<Pattern> buildExcludedPatternsList(String patterns, String separator) {
if (null != patterns && patterns.trim().length() != 0) {
List<Pattern> list = new ArrayList<>();
String[] tokens = patterns.split(separator);
for (String token : tokens) {
list.add(Pattern.compile(token.trim()));
}
return Collections.unmodifiableList(list);
} else {
return null;
}
}
}
@@ -20,6 +20,9 @@ package org.apache.struts2.dispatcher;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.ValueStack;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
@@ -27,13 +30,8 @@ import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
/**
* Contains preparation operations for a request before execution
@@ -223,21 +221,11 @@ public class PrepareOperations {
* Check whether the request matches a list of exclude patterns.
*
* @param request The request to check patterns against
* @param excludedPatterns list of patterns for exclusion
*
* @return <tt>true</tt> if the request URI matches one of the given patterns
*/
public boolean isUrlExcluded(HttpServletRequest request, List<Pattern> excludedPatterns) {
if (excludedPatterns == null) {
return false;
}
public boolean isUrlExcluded(HttpServletRequest request) {
String uri = RequestUtils.getUri(request);
for (Pattern pattern : excludedPatterns) {
if (pattern.matcher(uri).matches()) {
return true;
}
}
return false;
return dispatcher.getActionExcludedPatterns().stream().anyMatch(pattern -> pattern.matcher(uri).matches());
}
/**
@@ -18,6 +18,14 @@
*/
package org.apache.struts2.dispatcher.filter;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
@@ -28,17 +36,7 @@ import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
/**
* Handles both the preparation and execution phases of the Struts dispatching process. This filter is better to use
@@ -50,7 +48,6 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = createInitOperations();
@@ -62,8 +59,6 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
prepare = createPrepareOperations(dispatcher);
execute = createExecuteOperations(dispatcher);
// Note: Currently, excluded patterns are not refreshed following an XWork config reload
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
@@ -121,7 +116,7 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
try {
prepare.trackRecursion(request);
String uri = RequestUtils.getUri(request);
if (prepare.isUrlExcluded(request, excludedPatterns)) {
if (prepare.isUrlExcluded(request)) {
LOG.trace("Request: {} is excluded from handling by Struts, passing request to other filters", uri);
chain.doFilter(request, response);
} else {
@@ -18,11 +18,6 @@
*/
package org.apache.struts2.dispatcher.filter;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
@@ -31,9 +26,12 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
/**
* Prepares the request for execution by a later {@link org.apache.struts2.dispatcher.filter.StrutsExecuteFilter} filter instance.
@@ -43,7 +41,6 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
protected static final String REQUEST_EXCLUDED_FROM_ACTION_MAPPING = StrutsPrepareFilter.class.getName() + ".REQUEST_EXCLUDED_FROM_ACTION_MAPPING";
protected PrepareOperations prepare;
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = createInitOperations();
@@ -53,8 +50,6 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
dispatcher = init.initDispatcher(config);
prepare = createPrepareOperations(dispatcher);
// Note: Currently, excluded patterns are not refreshed following an XWork config reload
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
@@ -102,7 +97,7 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
boolean didWrap = false;
try {
prepare.trackRecursion(request);
if (prepare.isUrlExcluded(request, excludedPatterns)) {
if (prepare.isUrlExcluded(request)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, true);
} else {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, false);
@@ -372,8 +372,11 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
* @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);
List<String> values = parameters.get(name);
if (values == null) {
return null;
}
return values.toArray(new String[0]);
}
/* (non-Javadoc)
@@ -95,6 +95,7 @@ public interface MultiPartRequest {
/**
* Returns a list of all parameter values associated with a parameter name. If there is only
* one parameter value per name the resulting array will be of length 1.
* If the parameter doesn't exist, null should be returned instead of empty array.
*
* @param name the name of the parameter.
* @return an array of all values associated with the parameter name.
@@ -55,7 +55,7 @@ import java.util.Map;
* <p>
* The best way to add behavior to this interceptor is to utilize the {@link ParameterNameAware} interface in your
* actions. However, if you wish to apply a global rule that isn't implemented in your action, then you could extend
* this interceptor and override the {@link #acceptableName(String)} method.
* this interceptor and override the {@link #isAcceptableName(String)} method.
* </p>
*
* <!-- END SNIPPET: extending -->
@@ -26,11 +26,12 @@ import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import jakarta.servlet.http.Cookie;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.action.CookiesAware;
import jakarta.servlet.http.Cookie;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -357,7 +358,7 @@ public class CookieInterceptor extends AbstractInterceptor {
protected void injectIntoCookiesAwareAction(Object action, Map<String, String> cookiesMap) {
if (action instanceof CookiesAware) {
LOG.debug("Action [{}] implements CookiesAware, injecting cookies map [{}]", action, cookiesMap);
((CookiesAware)action).setCookiesMap(cookiesMap);
((CookiesAware)action).withCookies(cookiesMap);
}
if (action instanceof org.apache.struts2.action.CookiesAware) {
LOG.debug("Action [{}] implements CookiesAware, injecting cookies map [{}]", action, cookiesMap);
@@ -1,32 +0,0 @@
/*
* 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);
}
}
@@ -24,7 +24,6 @@ import com.opensymphony.xwork2.LocaleProviderFactory;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
@@ -85,7 +84,7 @@ public class I18nInterceptor extends AbstractInterceptor {
}
public void setLocaleStorage(String storageName) {
if (storageName == null || "".equals(storageName)) {
if (storageName == null || storageName.isEmpty()) {
this.storage = Storage.ACCEPT_LANGUAGE;
} else {
try {
@@ -169,27 +168,21 @@ public class I18nInterceptor extends AbstractInterceptor {
}
/**
* Creates a Locale object from the request param, which might
* be already a Local or a String
* Creates a Locale object from the request param
*
* @param requestedLocale the parameter from the request
* @return the Locale
* @return instance of {@link Locale} or null
*/
protected Locale getLocaleFromParam(Object requestedLocale) {
protected Locale getLocaleFromParam(String requestedLocale) {
LocaleProvider localeProvider = localeProviderFactory.createLocaleProvider();
Locale locale = null;
if (requestedLocale != null) {
if (requestedLocale instanceof Locale) {
locale = (Locale) requestedLocale;
} else {
String localeStr = requestedLocale.toString();
if (localeProvider.isValidLocaleString(localeStr)) {
locale = LocaleUtils.toLocale(localeStr);
} else {
locale = localeProvider.getLocale();
}
locale = localeProvider.toLocale(requestedLocale);
if (locale == null) {
locale = localeProvider.getLocale();
}
if (locale != null) {
LOG.debug("Found locale: {}", locale);
}
@@ -285,7 +278,7 @@ public class I18nInterceptor extends AbstractInterceptor {
@Override
@SuppressWarnings("rawtypes")
public Locale find() {
if (supportedLocale.size() > 0) {
if (!supportedLocale.isEmpty()) {
Enumeration locales = actionInvocation.getInvocationContext().getServletRequest().getLocales();
while (locales.hasMoreElements()) {
Locale locale = (Locale) locales.nextElement();
@@ -1,36 +0,0 @@
/*
* 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())));
}
}
@@ -1,30 +0,0 @@
/*
* 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);
}
}
@@ -18,10 +18,12 @@
*/
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.action.ApplicationAware;
import org.apache.struts2.action.ParametersAware;
@@ -32,15 +34,11 @@ import org.apache.struts2.action.ServletResponseAware;
import org.apache.struts2.action.SessionAware;
import org.apache.struts2.interceptor.servlet.ServletPrincipalProxy;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
/**
* <!-- START SNIPPET: description -->
* <p>
* An interceptor which sets action properties based on the interfaces an action implements. For example, if the action
* implements {@link ParameterAware} then the action context's parameter map will be set on it.
* implements {@link ParametersAware} then the action context's parameter map will be set on it.
* </p>
*
* <p>This interceptor is designed to set all properties an action needs if it's aware of servlet parameters, the
@@ -1,32 +0,0 @@
/*
* 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);
}
}
@@ -1,32 +0,0 @@
/*
* 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);
}
}
@@ -1,32 +0,0 @@
/*
* 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);
}
}
@@ -19,7 +19,9 @@
package org.apache.struts2.interceptor.csp;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.action.CspSettingsAware;
@@ -46,6 +48,9 @@ public final class CspInterceptor extends AbstractInterceptor {
private boolean prependServletContext = true;
private boolean enforcingMode;
private String reportUri;
private String reportTo;
private String cspSettingsClassName = DefaultCspSettings.class.getName();
@Override
public String intercept(ActionInvocation invocation) throws Exception {
@@ -54,12 +59,30 @@ public final class CspInterceptor extends AbstractInterceptor {
LOG.trace("Using CspSettings provided by the action: {}", action);
applySettings(invocation, ((CspSettingsAware) action).getCspSettings());
} else {
LOG.trace("Using DefaultCspSettings with action: {}", action);
applySettings(invocation, new DefaultCspSettings());
LOG.trace("Using {} with action: {}", cspSettingsClassName, action);
CspSettings cspSettings = createCspSettings(invocation);
applySettings(invocation, cspSettings);
}
return invocation.invoke();
}
private CspSettings createCspSettings(ActionInvocation invocation) throws ClassNotFoundException {
Class<?> cspSettingsClass;
try {
cspSettingsClass = ClassLoaderUtil.loadClass(cspSettingsClassName, getClass());
} catch (ClassNotFoundException e) {
throw new ConfigurationException(String.format("The class %s doesn't exist!", cspSettingsClassName));
}
if (!CspSettings.class.isAssignableFrom(cspSettingsClass)) {
throw new ConfigurationException(String.format("The class %s doesn't implement %s!",
cspSettingsClassName, CspSettings.class.getName()));
}
return (CspSettings) invocation.getInvocationContext().getContainer().inject(cspSettingsClass);
}
private void applySettings(ActionInvocation invocation, CspSettings cspSettings) {
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
@@ -76,6 +99,12 @@ public final class CspInterceptor extends AbstractInterceptor {
}
cspSettings.setReportUri(finalReportUri);
// apply reportTo if set
if (reportTo != null) {
LOG.trace("Applying: {} to reportTo", reportTo);
cspSettings.setReportTo(reportTo);
}
}
invocation.addPreResultListener((actionInvocation, resultCode) -> {
@@ -97,6 +126,17 @@ public final class CspInterceptor extends AbstractInterceptor {
this.reportUri = reportUri;
}
/**
* Sets the report group where csp violation reports will be sent. This will
* only be used if the reportUri is set.
*
* @param reportTo the report group where csp violation reports will be sent
* @since Struts 6.5.0
*/
public void setReportTo(String reportTo) {
this.reportTo = reportTo;
}
private Optional<URI> buildUri(String reportUri) {
try {
return Optional.of(URI.create(reportUri));
@@ -124,4 +164,13 @@ public final class CspInterceptor extends AbstractInterceptor {
this.prependServletContext = prependServletContext;
}
/**
* Sets the class name of the default {@link CspSettings} implementation to use when the action does not
* set its own values. If not set, the default is {@link DefaultCspSettings}.
*
* @since Struts 6.5.0
*/
public void setCspSettingsClassName(String cspSettingsClassName) {
this.cspSettingsClassName = cspSettingsClassName;
}
}
@@ -37,6 +37,7 @@ public interface CspSettings {
String SCRIPT_SRC = "script-src";
String BASE_URI = "base-uri";
String REPORT_URI = "report-uri";
String REPORT_TO = "report-to";
String NONE = "none";
String STRICT_DYNAMIC = "strict-dynamic";
String HTTP = "http:";
@@ -56,6 +57,13 @@ public interface CspSettings {
*/
void setReportUri(String uri);
/**
* Sets the report group where csp violation reports will be sent
*
* @since Struts 6.5.0
*/
void setReportTo(String group);
/**
* Sets CSP headers in enforcing mode when true, and report-only when false
*/
@@ -20,6 +20,7 @@ package org.apache.struts2.interceptor.csp;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.action.CspSettingsAware;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -31,7 +32,11 @@ import static java.lang.String.format;
/**
* Default implementation of {@link CspSettings}.
* The default policy implements strict CSP with a nonce based approach and follows the guide: <a href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html/</a>
* The default policy implements strict CSP with a nonce based approach and follows the guide:
* <a href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html/</a>
* You may extend or replace this class if you wish to customize the default policy further, and use your class
* by setting the {@link CspInterceptor} defaultCspSettingsClassName parameter. Actions that
* implement the {@link CspSettingsAware} interface will ignore the defaultCspSettingsClassName parameter.
*
* @see CspSettings
* @see CspInterceptor
@@ -42,20 +47,22 @@ public class DefaultCspSettings implements CspSettings {
private final SecureRandom sRand = new SecureRandom();
private String reportUri;
protected String reportUri;
protected String reportTo;
// default to reporting mode
private String cspHeader = CSP_REPORT_HEADER;
protected String cspHeader = CSP_REPORT_HEADER;
@Override
public void addCspHeaders(HttpServletResponse response) {
throw new UnsupportedOperationException("Unsupported implementation, use #addCspHeaders(HttpServletRequest request, HttpServletResponse response)");
}
@Override
public void addCspHeaders(HttpServletRequest request, HttpServletResponse response) {
if (isSessionActive(request)) {
LOG.trace("Session is active, applying CSP settings");
associateNonceWithSession(request);
response.setHeader(cspHeader, cratePolicyFormat(request));
response.setHeader(cspHeader, createPolicyFormat(request));
} else {
LOG.trace("Session is not active, ignoring CSP settings");
}
@@ -70,7 +77,7 @@ public class DefaultCspSettings implements CspSettings {
request.getSession().setAttribute("nonce", nonceValue);
}
private String cratePolicyFormat(HttpServletRequest request) {
protected String createPolicyFormat(HttpServletRequest request) {
StringBuilder policyFormatBuilder = new StringBuilder()
.append(OBJECT_SRC)
.append(format(" '%s'; ", NONE))
@@ -84,13 +91,18 @@ public class DefaultCspSettings implements CspSettings {
if (reportUri != null) {
policyFormatBuilder
.append(REPORT_URI)
.append(format(" %s", reportUri));
.append(format(" %s; ", reportUri));
if(reportTo != null) {
policyFormatBuilder
.append(REPORT_TO)
.append(format(" %s; ", reportTo));
}
}
return format(policyFormatBuilder.toString(), getNonceString(request));
}
private String getNonceString(HttpServletRequest request) {
protected String getNonceString(HttpServletRequest request) {
Object nonce = request.getSession().getAttribute("nonce");
return Objects.toString(nonce);
}
@@ -101,20 +113,28 @@ public class DefaultCspSettings implements CspSettings {
return ret;
}
@Override
public void setEnforcingMode(boolean enforcingMode) {
if (enforcingMode) {
cspHeader = CSP_ENFORCE_HEADER;
}
}
@Override
public void setReportUri(String reportUri) {
this.reportUri = reportUri;
}
@Override
public void setReportTo(String reportTo) {
this.reportTo = reportTo;
}
@Override
public String toString() {
return "DefaultCspSettings{" +
"reportUri='" + reportUri + '\'' +
", reportTo='" + reportTo + '\'' +
", cspHeader='" + cspHeader + '\'' +
'}';
}
@@ -20,6 +20,8 @@ package org.apache.struts2.interceptor.exec;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.Serializable;
@@ -30,6 +32,8 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
private static final long serialVersionUID = 3884464776311686443L;
private static final Logger LOG = LogManager.getLogger(StrutsBackgroundProcess.class);
private final String threadName;
private final int threadPriority;
@@ -44,8 +48,8 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
/**
* Constructs a background process
*
* @param invocation The action invocation
* @param threadName The name of background thread
* @param invocation The action invocation
* @param threadName The name of background thread
* @param threadPriority The priority of background thread
*/
public StrutsBackgroundProcess(ActionInvocation invocation, String threadName, int threadPriority) {
@@ -61,11 +65,19 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
try {
beforeInvocation();
result = invocation.invokeActionOnly();
afterInvocation();
} catch (Exception e) {
LOG.warn("Exception during invokeActionOnly() execution", e);
exception = e;
} finally {
done = true;
try {
afterInvocation();
} catch (Exception ex) {
if (exception == null) {
exception = ex;
}
LOG.warn("Exception during afterInvocation() execution", ex);
}
done = true;
}
});
processThread.setName(threadName);
@@ -20,10 +20,8 @@ package org.apache.struts2.interceptor.parameter;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.TextProvider;
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;
@@ -56,7 +54,6 @@ 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;
@@ -67,6 +64,8 @@ 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 com.opensymphony.xwork2.util.DebugUtils.notifyDeveloperOfError;
import static java.lang.String.format;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.indexOfAny;
@@ -196,7 +195,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
Map<String, Object> contextMap = actionContext.getContextMap();
batchApplyReflectionContextState(contextMap, true);
try {
setParameters(action, actionContext.getValueStack(), parameters);
applyParameters(action, actionContext.getValueStack(), parameters);
} finally {
batchApplyReflectionContextState(contextMap, false);
}
@@ -227,14 +226,6 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
protected void addParametersToContext(ActionContext ac, Map<String, ?> newParams) {
}
/**
* @deprecated since 6.4.0, use {@link #applyParameters}
*/
@Deprecated
protected void setParameters(final Object action, ValueStack stack, HttpParameters parameters) {
applyParameters(action, stack, parameters);
}
protected void applyParameters(final Object action, ValueStack stack, HttpParameters parameters) {
Map<String, Parameter> acceptableParameters = toAcceptableParameters(parameters, action);
@@ -317,19 +308,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
}
protected void notifyDeveloperParameterException(Object action, String property, String message) {
String logMsg = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
if (action instanceof TextProvider) {
TextProvider tp = (TextProvider) action;
logMsg = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{logMsg});
}
LOG.error(logMsg);
if (action instanceof ValidationAware) {
ValidationAware validationAware = (ValidationAware) action;
Collection<String> messages = validationAware.getActionMessages();
messages.add(message);
validationAware.setActionMessages(messages);
}
String logMsg = format("Unexpected Exception caught setting '%s' on '%s: %s", property, action.getClass(), message);
notifyDeveloperOfError(LOG, action, logMsg);
}
/**
@@ -340,7 +320,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameter(String name, Object action) {
return acceptableName(name) && isAcceptableParameterNameAware(name, action) && isParameterAnnotatedAndAllowlist(name, action);
return isAcceptableName(name) && isAcceptableParameterNameAware(name, action) && isParameterAnnotatedAndAllowlist(name, action);
}
protected boolean isAcceptableParameterNameAware(String name, Object action) {
@@ -388,23 +368,37 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return hasValidAnnotatedField(action, rootProperty, paramDepth);
}
if (hasValidAnnotatedPropertyDescriptor(propDescOpt.get(), paramDepth)) {
if (hasValidAnnotatedPropertyDescriptor(action, propDescOpt.get(), paramDepth)) {
return true;
}
return hasValidAnnotatedField(action, rootProperty, paramDepth);
}
/**
* @deprecated since 6.5.0, use {@link #hasValidAnnotatedPropertyDescriptor(Object, PropertyDescriptor, long)}
* instead.
*/
@Deprecated
protected boolean hasValidAnnotatedPropertyDescriptor(PropertyDescriptor propDesc, long paramDepth) {
return hasValidAnnotatedPropertyDescriptor(null, propDesc, paramDepth);
}
protected boolean hasValidAnnotatedPropertyDescriptor(Object action, 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'.",
String logMessage = format(
"Parameter injection for method [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
relevantMethod.getName(),
relevantMethod.getDeclaringClass().getName());
if (devMode) {
notifyDeveloperOfError(LOG, action, logMessage);
} else {
LOG.debug(logMessage);
}
return false;
}
if (paramDepth >= 1) {
@@ -455,10 +449,15 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
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'.",
String logMessage = format(
"Parameter injection for field [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
fieldName,
action.getClass().getName());
if (devMode) {
notifyDeveloperOfError(LOG, action, logMessage);
} else {
LOG.debug(logMessage);
}
return false;
}
if (paramDepth >= 1) {
@@ -510,7 +509,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
* @return true if parameter is accepted
*/
protected boolean isAcceptableParameterValue(Parameter param, Object action) {
return isAcceptableParameterValueAware(param, action) && acceptableValue(param.getName(), param.getValue());
return isAcceptableParameterValueAware(param, action) && isAcceptableValue(param.getName(), param.getValue());
}
protected boolean isAcceptableParameterValueAware(Parameter param, Object action) {
@@ -533,17 +532,10 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return "NONE";
}
return parameters.entrySet().stream()
.map(entry -> String.format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
.map(entry -> format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
.collect(joining());
}
/**
* @deprecated since 6.4.0, use {@link #isAcceptableName}
*/
protected boolean acceptableName(String name) {
return isAcceptableName(name);
}
/**
* Validates the name passed is:
* * Within the max length of a parameter name
@@ -572,13 +564,6 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
return DMI_IGNORED_PATTERN.matcher(name).matches();
}
/**
* @deprecated since 6.4.0, use {@link #isAcceptableValue}
*/
protected boolean acceptableValue(String name, String value) {
return isAcceptableValue(name, value);
}
/**
* Validates:
* * Value is null/blank
@@ -22,15 +22,16 @@ import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.emptyMap;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_ENABLE;
/**
* Generic test setup methods to be used with any unit testing framework.
@@ -38,7 +39,9 @@ import static java.util.Collections.emptyMap;
public class StrutsTestCaseHelper {
public static Dispatcher initDispatcher(ServletContext ctx, Map<String, String> params) {
Dispatcher du = new DispatcherWrapper(ctx, params != null ? params : emptyMap());
Map<String, String> finalParams = params != null ? new HashMap<>(params) : new HashMap<>();
finalParams.putIfAbsent(STRUTS_ALLOWLIST_ENABLE, "false");
Dispatcher du = new DispatcherWrapper(ctx, finalParams);
du.init();
Dispatcher.setInstance(du);
@@ -28,14 +28,14 @@ import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.cache.WebappTemplateLoader;
import freemarker.ext.jakarta.servlet.WebappTemplateLoader;
import freemarker.core.HTMLOutputFormat;
import freemarker.core.TemplateClassResolver;
import freemarker.ext.jsp.TaglibFactory;
import freemarker.ext.servlet.HttpRequestHashModel;
import freemarker.ext.servlet.HttpRequestParametersHashModel;
import freemarker.ext.servlet.HttpSessionHashModel;
import freemarker.ext.servlet.ServletContextHashModel;
import freemarker.ext.jakarta.jsp.TaglibFactory;
import freemarker.ext.jakarta.servlet.HttpRequestHashModel;
import freemarker.ext.jakarta.servlet.HttpRequestParametersHashModel;
import freemarker.ext.jakarta.servlet.HttpSessionHashModel;
import freemarker.ext.jakarta.servlet.ServletContextHashModel;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.TemplateException;
@@ -118,9 +118,6 @@ public class FreemarkerManager {
public static final String INITPARAM_DEBUG = "Debug";
public static final String KEY_REQUEST = "Request";
public static final String KEY_INCLUDE = "include_page";
public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
public static final String KEY_SESSION = "Session";
public static final String KEY_APPLICATION = "Application";
public static final String KEY_APPLICATION_PRIVATE = "__FreeMarkerServlet.Application__";
@@ -138,10 +135,29 @@ public class FreemarkerManager {
// for Struts
public static final String KEY_REQUEST_PARAMETERS_STRUTS = "Parameters";
public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
public static final String EXPIRATION_DATE;
/**
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
*/
@Deprecated
public static final String KEY_INCLUDE = "include_page";
/**
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
*/
@Deprecated
public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
/**
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
*/
@Deprecated
public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
/**
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
*/
@Deprecated
public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
/**
* Adds individual settings.
*
@@ -212,10 +212,33 @@ struts.freemarker.mru.max.strong.size=0
struts.xslt.nocache=false
### Whether to always select the namespace to be everything before the last slash or not
struts.mapper.alwaysSelectFullNamespace=false
struts.mapper.alwaysSelectFullNamespace=true
### Whether to allow static field access in OGNL expressions or not
struts.ognl.allowStaticFieldAccess=true
### Whether to fallback to empty namespace when request namespace does not match any in configuration
struts.actionConfig.fallbackToEmptyNamespace=false
### Whether to allow static field access in OGNL expressions, not recommended!
struts.ognl.allowStaticFieldAccess=false
### Whether OGNL can access these types of classes, not recommended!
struts.disallowDefaultPackageAccess=true
struts.disallowProxyObjectAccess=true
### Whether OGNL can instantiate arbitrary map classes, not recommended!
struts.ognl.disallowCustomOgnlMap=true
### OGNL context access, highly recommended to uncomment if context access from expressions not needed!
# struts.ognl.valueStackFallbackToContext=false
# struts.ognl.excludedNodeTypes=ognl.ASTRootVarRef,ognl.ASTThisVarRef,ognl.ASTVarRef
### Whether to enable OGNL allowlist, protects against critical vulnerabilities, strongly recommended to keep enabled!
struts.allowlist.enable=true
### Whether to require @StrutsParameter annotations on Action parameter injection methods
### Synergizes with OGNL allowlist, strongly recommended to keep enabled!
struts.parameters.requireAnnotations=true
### Whether to drop @StrutsParameter annotation requirement on simple setter methods
### Useful for transitioning legacy applications, but highly recommended to set to false as soon as possible!
struts.parameters.requireAnnotations.transitionMode=false
### Whether to throw a RuntimeException when a property is not found
### in an expression, or when the expression evaluation fails
+158
View File
@@ -0,0 +1,158 @@
<?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.
*/
-->
<!-- START SNIPPET: strutsDtd -->
<!--
Struts configuration DTD.
Use the following DOCTYPE
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
"https://struts.apache.org/dtds/struts-6.5.dtd">
-->
<!ELEMENT struts ((package|include|bean|constant)*,bean-selection?, unknown-handler-stack?)>
<!ATTLIST struts
order CDATA #IMPLIED
>
<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
<!ATTLIST package
name CDATA #REQUIRED
extends CDATA #IMPLIED
namespace CDATA #IMPLIED
abstract CDATA #IMPLIED
final CDATA #IMPLIED
strict-method-invocation (true|false) "true"
>
<!ELEMENT result-types (result-type+)>
<!ELEMENT result-type (param*)>
<!ATTLIST result-type
name CDATA #REQUIRED
class CDATA #REQUIRED
default (true|false) "false"
>
<!ELEMENT interceptors (interceptor|interceptor-stack)+>
<!ELEMENT interceptor (param*)>
<!ATTLIST interceptor
name CDATA #REQUIRED
class CDATA #REQUIRED
>
<!ELEMENT interceptor-stack (interceptor-ref*)>
<!ATTLIST interceptor-stack
name CDATA #REQUIRED
>
<!ELEMENT interceptor-ref (param*)>
<!ATTLIST interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-interceptor-ref (#PCDATA)>
<!ATTLIST default-interceptor-ref
name CDATA #REQUIRED
>
<!ELEMENT default-action-ref (#PCDATA)>
<!ATTLIST default-action-ref
name CDATA #REQUIRED
>
<!ELEMENT default-class-ref (#PCDATA)>
<!ATTLIST default-class-ref
class CDATA #REQUIRED
>
<!ELEMENT global-results (result+)>
<!ELEMENT global-allowed-methods (#PCDATA)>
<!ELEMENT global-exception-mappings (exception-mapping+)>
<!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)>
<!ATTLIST action
name CDATA #REQUIRED
class CDATA #IMPLIED
method CDATA #IMPLIED
converter CDATA #IMPLIED
>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param
name CDATA #REQUIRED
>
<!ELEMENT result (#PCDATA|param)*>
<!ATTLIST result
name CDATA #IMPLIED
type CDATA #IMPLIED
>
<!ELEMENT exception-mapping (#PCDATA|param)*>
<!ATTLIST exception-mapping
name CDATA #IMPLIED
exception CDATA #REQUIRED
result CDATA #REQUIRED
>
<!ELEMENT allowed-methods (#PCDATA)>
<!ELEMENT include (#PCDATA)>
<!ATTLIST include
file CDATA #REQUIRED
>
<!ELEMENT bean (#PCDATA)>
<!ATTLIST bean
type CDATA #IMPLIED
name CDATA #IMPLIED
class CDATA #REQUIRED
scope CDATA #IMPLIED
static CDATA #IMPLIED
optional CDATA #IMPLIED
>
<!ELEMENT bean-selection (#PCDATA)>
<!ATTLIST bean-selection
name CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT constant (#PCDATA)>
<!ATTLIST constant
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT unknown-handler-stack (unknown-handler-ref*)>
<!ELEMENT unknown-handler-ref (#PCDATA)>
<!ATTLIST unknown-handler-ref
name CDATA #REQUIRED
>
<!-- END SNIPPET: strutsDtd -->
@@ -92,6 +92,7 @@
org.apache.catalina.core,
org.apache.commons.beanutils,
org.apache.commons.collections,
org.apache.jasper,
org.apache.struts2.ognl,
org.apache.tomcat,
org.apache.velocity,
@@ -124,6 +125,7 @@
org.apache.catalina.core,
org.apache.commons.beanutils,
org.apache.commons.collections,
org.apache.jasper,
org.apache.struts2.ognl,
org.apache.tomcat,
org.apache.velocity,
@@ -26,14 +26,10 @@ import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.mock.MockActionProxy;
import com.opensymphony.xwork2.mock.MockInterceptor;
import com.opensymphony.xwork2.mock.MockResult;
import com.opensymphony.xwork2.ognl.DefaultOgnlBeanInfoCacheFactory;
import com.opensymphony.xwork2.ognl.DefaultOgnlExpressionCacheFactory;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.ognl.StrutsOgnlGuard;
import java.util.ArrayList;
import java.util.HashMap;
@@ -42,6 +38,8 @@ import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import static com.opensymphony.xwork2.ognl.OgnlUtilTest.createOgnlUtil;
/**
* A partial test of DefaultActionInvocation.
@@ -531,14 +529,6 @@ public class DefaultActionInvocationTest extends XWorkTestCase {
loadConfigurationProviders(configurationProvider);
}
private OgnlUtil createOgnlUtil() {
return new OgnlUtil(
new DefaultOgnlExpressionCacheFactory<>(),
new DefaultOgnlBeanInfoCacheFactory<>(),
new StrutsOgnlGuard()
);
}
private static class SimpleActionEventListener implements ActionEventListener {
private final String name;
@@ -0,0 +1,174 @@
/*
* 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 com.opensymphony.xwork2;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Locale;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class DefaultLocaleProviderTest {
private DefaultLocaleProvider provider;
@Before
public void setUp() throws Exception {
provider = new DefaultLocaleProvider();
}
@BeforeClass
public static void beforeClass() throws Exception {
ActionContext.of().bind();
}
@AfterClass
public static void afterClass() throws Exception {
ActionContext.clear();
}
@Test
public void getLocale() {
// given
ActionContext.getContext().withLocale(Locale.ITALY);
// when
Locale actual = provider.getLocale();
// then
assertEquals(Locale.ITALY, actual);
}
@Test
public void getLocaleNull() {
// given
ActionContext backup = ActionContext.getContext();
ActionContext.clear();
// when
Locale actual = provider.getLocale();
// then
assertNull(actual);
ActionContext.bind(backup);
}
@Test
public void toLocale() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
Locale actual = provider.toLocale("it");
// then
assertEquals(Locale.ITALIAN, actual);
}
@Test
public void toLocaleFull() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
Locale actual = provider.toLocale("it_IT");
// then
assertEquals(Locale.ITALY, actual);
}
@Test
public void toLocaleTrimEndOfLine() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
Locale actual = provider.toLocale("it_IT\n");
// then
assertEquals(Locale.ITALY, actual);
}
@Test
public void toLocaleTrimEmptySpace() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
Locale actual = provider.toLocale(" it_IT ");
// then
assertEquals(Locale.ITALY, actual);
}
@Test
public void isValidLocaleNull() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
boolean actual = provider.isValidLocale(null);
// then
assertFalse(actual);
}
@Test
public void isValidLocale() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
boolean actual = provider.isValidLocale(Locale.ITALIAN);
// then
assertTrue(actual);
}
@Test
public void isValidLocaleString() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
boolean actual = provider.isValidLocaleString("it");
// then
assertTrue(actual);
}
@Test
public void isValidLocaleStringNot() {
// given
ActionContext.getContext().withLocale(Locale.GERMAN);
// when
boolean actual = provider.isValidLocaleString("italy");
// then
assertFalse(actual);
}
}
@@ -0,0 +1,82 @@
/*
* 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 com.opensymphony.xwork2;
import org.junit.Test;
import java.util.Locale;
import static org.junit.Assert.*;
public class LocaleProviderTest {
@Test
public void toLocale() {
// given
DummyLocale locale = new DummyLocale();
// when
Locale actual = locale.toLocale("de");
// then
assertEquals(Locale.GERMAN, actual);
}
@Test
public void toLocaleTrim() {
// given
DummyLocale locale = new DummyLocale();
// when
Locale actual = locale.toLocale(" de_DE ");
// then
assertEquals(Locale.GERMANY, actual);
}
@Test
public void toLocaleNull() {
// given
DummyLocale locale = new DummyLocale();
// when
Locale actual = locale.toLocale("germany");
// then
assertNull(actual);
}
}
class DummyLocale implements LocaleProvider {
@Override
public Locale getLocale() {
return null;
}
@Override
public boolean isValidLocaleString(String localeStr) {
return false;
}
@Override
public boolean isValidLocale(Locale locale) {
return false;
}
}
@@ -31,6 +31,7 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.mock.MockInterceptor;
import com.opensymphony.xwork2.test.StubConfigurationProvider;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.HttpParameters;
@@ -239,6 +240,41 @@ public class ConfigurationTest extends XWorkTestCase {
mockContainerProvider.verify();
}
public void testGetActionConfigFallbackToEmptyNamespaceWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsEnabled() {
// struts.actionConfig.fallbackToEmptyNamespace default to true, so it is enabled
RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
// check namespace that doesn't match fallback to empty namespace
ActionConfig actionConfig = configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo");
assertEquals("default", actionConfig.getPackageName()); // fallback to empty namespace (package name is default)
assertEquals("LazyFoo", actionConfig.getName());
// check non-empty namespace and name in config still matches
assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
// check root namespace and name in config still matches
actionConfig = configuration.getActionConfig("/", "LazyFoo");
assertEquals("default", actionConfig.getPackageName());
assertEquals("LazyFoo", actionConfig.getName());
}
public void testGetActionConfigReturnNullWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsDisabled() {
// set the struts.actionConfig.fallbackToEmptyNamespace to false and reload the configuration
setStrutsConstant(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, "false");
RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
// check namespace that doesn't match NOT fallback to empty namespace and return null
assertNull(configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo"));
// check non-empty namespace and name in config still matches
assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
// check root namespace and name in config still matches
ActionConfig actionConfig = configuration.getActionConfig("/", "LazyFoo");
assertEquals("default", actionConfig.getPackageName());
assertEquals("LazyFoo", actionConfig.getName());
}
public void testInitForPackageProviders() {
loadConfigurationProviders(new StubConfigurationProvider() {
@@ -36,10 +36,9 @@ import java.util.List;
public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase {
public void testBadInheritance() throws ConfigurationException {
final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml";
ConfigurationProvider provider = null;
try {
provider = buildConfigurationProvider(filename);
provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-bad-inheritance.xml"));
fail("Should have thrown a ConfigurationException");
provider.init(configuration);
provider.loadPackages();
@@ -49,8 +48,7 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
public void testBasicPackages() throws ConfigurationException {
final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml";
ConfigurationProvider provider = buildConfigurationProvider(filename);
ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-basic-packages.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -70,8 +68,7 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
public void testDefaultPackage() throws ConfigurationException {
final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml";
ConfigurationProvider provider = buildConfigurationProvider(filename);
ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-default-package.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -84,8 +81,7 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
public void testPackageInheritance() throws ConfigurationException {
final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml";
ConfigurationProvider provider = buildConfigurationProvider(filename);
ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-inheritance.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -111,7 +107,7 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
assertTrue(multipleParents.contains(defaultPackage));
assertTrue(multipleParents.contains(abstractPackage));
assertTrue(multipleParents.contains(singlePackage));
PackageConfig parentBelow = configuration.getPackageConfig("testParentBelow");
assertEquals(1, parentBelow.getParents().size());
List<PackageConfig> parentBelowParents = parentBelow.getParents();
@@ -129,7 +125,7 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
assertNull(runtimeConfiguration.getActionConfig("/single", "abstract"));
assertNotNull(runtimeConfiguration.getActionConfig("/single", "single"));
assertNull(runtimeConfiguration.getActionConfig("/single", "multiple"));
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "default"));
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "abstract"));
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "single"));
@@ -138,13 +134,57 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
public void testPackageWithFinalAttributeLoads() throws ConfigurationException {
ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-final.xml"));
provider.init(configuration);
provider.loadPackages();
// test expectations
assertEquals(3, configuration.getPackageConfigs().size());
PackageConfig defaultPackage = configuration.getPackageConfig("default");
assertNotNull(defaultPackage);
assertEquals("default", defaultPackage.getName());
// final package extends default
PackageConfig finalPackage = configuration.getPackageConfig("finalPackage");
assertNotNull(finalPackage);
assertEquals("finalPackage", finalPackage.getName());
assertEquals(1, finalPackage.getParents().size());
assertEquals(defaultPackage, finalPackage.getParents().get(0));
// normal package extends default
PackageConfig normalPackage = configuration.getPackageConfig("normalPackage");
assertNotNull(normalPackage);
assertEquals("normalPackage", normalPackage.getName());
assertEquals(1, normalPackage.getParents().size());
assertEquals(defaultPackage, normalPackage.getParents().get(0));
configurationManager.addContainerProvider(provider);
configurationManager.reload();
RuntimeConfiguration runtimeConfiguration = configurationManager.getConfiguration().getRuntimeConfiguration();
assertNotNull(runtimeConfiguration.getActionConfig("/final", "default"));
assertNotNull(runtimeConfiguration.getActionConfig("/final", "actionFinal"));
assertNotNull(runtimeConfiguration.getActionConfig("/normal", "default"));
assertNotNull(runtimeConfiguration.getActionConfig("/normal", "actionNormal"));
}
public void testExtendsFinalPackageThrowsConfigurationException() throws ConfigurationException {
try {
buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-extends-final.xml"));
} catch (ConfigurationException e) {
assertEquals("Parent package is final and unextendable: parentLevelTwo", e.getMessage());
}
}
public void testDefaultClassRef() throws ConfigurationException {
final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml";
final String hasDefaultClassRefPkgName = "hasDefaultClassRef";
final String noDefaultClassRefPkgName = "noDefaultClassRef";
final String testDefaultClassRef = "com.opensymphony.xwork2.ActionSupport";
ConfigurationProvider provider = buildConfigurationProvider(filename);
ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-defaultclassref-package.xml"));
provider.init(configuration);
// setup our expectations
@@ -157,4 +197,8 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
assertEquals(expectedDefaultClassRefPackage, configuration.getPackageConfig(hasDefaultClassRefPkgName));
assertEquals(expectedNoDefaultClassRefPackage, configuration.getPackageConfig(noDefaultClassRefPkgName));
}
private String getXmlConfigFilePath(String fileName) {
return "com/opensymphony/xwork2/config/providers/" + fileName;
}
}
@@ -63,6 +63,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import static com.opensymphony.xwork2.ognl.OgnlCacheFactory.CacheType.BASIC;
import static com.opensymphony.xwork2.ognl.OgnlCacheFactory.CacheType.LRU;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThrows;
@@ -80,6 +82,11 @@ public class OgnlUtilTest extends XWorkTestCase {
ognlUtil = container.getInstance(OgnlUtil.class);
}
private void resetOgnlUtil(Map<String, ?> properties) {
loadButSet(properties);
ognlUtil = container.getInstance(OgnlUtil.class);
}
public void testCanSetADependentObject() {
String dogName = "fido";
@@ -1152,8 +1159,8 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Object.class.getName());
ognlUtil.setValue("class.classLoader.defaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
// Object.class is excluded by default
ognlUtil.setValue("class.classLoader", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
expected = e;
@@ -1166,9 +1173,11 @@ public class OgnlUtilTest extends XWorkTestCase {
public void testAllowCallingMethodsOnObjectClassInDevModeTrue() {
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Foo.class.getName());
ognlUtil.setDevModeExcludedClasses("");
ognlUtil.setDevMode(Boolean.TRUE.toString());
Map<String, String> properties = new HashMap<>();
properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, Foo.class.getName());
properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, "");
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
resetOgnlUtil(properties);
Foo foo = new Foo();
String result = (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class);
@@ -1180,14 +1189,18 @@ public class OgnlUtilTest extends XWorkTestCase {
}
public void testExclusionListDevModeOnOff() throws Exception {
ognlUtil.setDevModeExcludedClasses(Foo.class.getName());
Foo foo = new Foo();
ognlUtil.setDevMode(Boolean.TRUE.toString());
Map<String, String> properties = new HashMap<>();
properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, Foo.class.getName());
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
resetOgnlUtil(properties);
OgnlException e = assertThrows(OgnlException.class, () -> ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
assertThat(e).hasMessageContaining("com.opensymphony.xwork2.util.Foo.toString");
ognlUtil.setDevMode(Boolean.FALSE.toString());
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.FALSE.toString());
resetOgnlUtil(properties);
assertEquals("Foo", (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
}
@@ -1196,7 +1209,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Object.class.getName());
// Object.class is excluded by default
ognlUtil.setValue("Class.ClassLoader.DefaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1212,7 +1225,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Object.class.getName());
// Object.class is excluded by default
ognlUtil.setValue("class['classLoader']['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1243,7 +1256,7 @@ public class OgnlUtilTest extends XWorkTestCase {
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Object.class.getName());
// Object.class is excluded by default
ognlUtil.setValue("class[\"classLoader\"]['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1284,12 +1297,11 @@ public class OgnlUtilTest extends XWorkTestCase {
assertEquals(expected.getMessage(), "Inappropriate OGNL expression: toString()");
}
public void testAvoidCallingSomeClasses() {
public void testStaticMethodBlocked() {
Foo foo = new Foo();
Exception expected = null;
try {
ognlUtil.setExcludedClasses(Runtime.class.getName());
ognlUtil.setValue("@java.lang.Runtime@getRuntime().exec('mate')", ognlUtil.createDefaultContext(foo), foo, true);
fail();
} catch (OgnlException e) {
@@ -1333,13 +1345,13 @@ public class OgnlUtilTest extends XWorkTestCase {
public void testDefaultOgnlUtilAlternateConstructorArguments() {
// Code coverage test for the OgnlUtil alternate constructor method, and verify expected behaviour.
try {
OgnlUtil basicOgnlUtil = new OgnlUtil(new DefaultOgnlExpressionCacheFactory<>(), null, null);
new OgnlUtil(createDefaultOgnlExpressionCacheFactory(), null, null);
fail("null beanInfoCacheFactory should result in exception");
} catch (NullPointerException iaex) {
// expected result
}
try {
OgnlUtil basicOgnlUtil = new OgnlUtil(null, new DefaultOgnlBeanInfoCacheFactory<>(), null);
new OgnlUtil(null, createDefaultOgnlBeanInfoCacheFactory(), null);
fail("null expressionCacheFactory should result in exception");
} catch (NullPointerException iaex) {
// expected result
@@ -1604,23 +1616,24 @@ public class OgnlUtilTest extends XWorkTestCase {
*/
public void testOgnlDefaultCacheFactoryCoverage() {
OgnlCache<String, Object> ognlCache;
DefaultOgnlCacheFactory defaultOgnlCacheFactory = new DefaultOgnlCacheFactory<String, Object>();
// Normal cache
defaultOgnlCacheFactory.setCacheMaxSize("12");
defaultOgnlCacheFactory.setUseLRUCache("false");
DefaultOgnlCacheFactory<String, Object> defaultOgnlCacheFactory = new DefaultOgnlCacheFactory<>(12, BASIC);
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 12, ognlCache.getEvictionLimit());
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(6, 6, 0.75f, false);
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(6, 6, 0.75f, BASIC);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 6, ognlCache.getEvictionLimit());
// LRU cache
defaultOgnlCacheFactory.setCacheMaxSize("30");
defaultOgnlCacheFactory.setUseLRUCache("true");
defaultOgnlCacheFactory = new DefaultOgnlCacheFactory<>(30, LRU);
ognlCache = defaultOgnlCacheFactory.buildOgnlCache();
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 30, ognlCache.getEvictionLimit());
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(15, 15, 0.75f, true);
ognlCache = defaultOgnlCacheFactory.buildOgnlCache(15, 15, 0.75f, LRU);
assertNotNull("No param build method result null ?", ognlCache);
assertEquals("Eviction limit for cache mismatches limit for factory ?", 15, ognlCache.getEvictionLimit());
}
@@ -1643,12 +1656,8 @@ public class OgnlUtilTest extends XWorkTestCase {
*/
private OgnlUtil generateOgnlUtilInstanceWithDefaultLRUCacheFactories() {
final OgnlUtil result;
final DefaultOgnlExpressionCacheFactory<String, Object> expressionFactory = new DefaultOgnlExpressionCacheFactory<>();
final DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo> beanInfoFactory = new DefaultOgnlBeanInfoCacheFactory<>();
expressionFactory.setUseLRUCache("true");
expressionFactory.setCacheMaxSize("25");
beanInfoFactory.setUseLRUCache("true");
beanInfoFactory.setCacheMaxSize("25");
final DefaultOgnlExpressionCacheFactory<String, Object> expressionFactory = new DefaultOgnlExpressionCacheFactory<>(String.valueOf(25), LRU.toString());
final DefaultOgnlBeanInfoCacheFactory<Class<?>, BeanInfo> beanInfoFactory = new DefaultOgnlBeanInfoCacheFactory<>(String.valueOf(25), LRU.toString());
result = new OgnlUtil(expressionFactory, beanInfoFactory, new StrutsOgnlGuard());
return result;
}
@@ -1796,4 +1805,19 @@ public class OgnlUtilTest extends XWorkTestCase {
}
}
public static OgnlUtil createOgnlUtil() {
return new OgnlUtil(
createDefaultOgnlExpressionCacheFactory(),
createDefaultOgnlBeanInfoCacheFactory(),
new StrutsOgnlGuard()
);
}
public static <K, V> DefaultOgnlExpressionCacheFactory<K, V> createDefaultOgnlExpressionCacheFactory() {
return new DefaultOgnlExpressionCacheFactory<>(String.valueOf(10_000), BASIC.toString());
}
public static <K, V> DefaultOgnlBeanInfoCacheFactory<K, V> createDefaultOgnlBeanInfoCacheFactory() {
return new DefaultOgnlBeanInfoCacheFactory<>(String.valueOf(10_000), BASIC.toString());
}
}
@@ -437,12 +437,12 @@ public class OgnlValueStackTest extends XWorkTestCase {
}
/**
* Allow access Enums without enabling access to static methods
* Enum methods should also be banned alongside static methods
*/
public void testEnum() throws Exception {
assertEquals("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
assertEquals("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
assertEquals("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
assertNull("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
assertNull("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
assertNull("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
}
public void testStaticMethodDisallow() {
@@ -1061,7 +1061,7 @@ public class OgnlValueStackTest extends XWorkTestCase {
OgnlValueStack stack2 = new OgnlValueStack(vs,
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(RootAccessor.class), true);
(CompoundRootAccessor) container.getInstance(RootAccessor.class), new SecurityMemberAccess(true));
container.inject(stack2);
assertEquals(vs.getRoot(), stack2.getRoot());
@@ -26,12 +26,16 @@ import ognl.MemberAccess;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.struts2.ognl.ProviderAllowlist;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -413,7 +417,7 @@ public class SecurityMemberAccessTest {
boolean actual = sma.isAccessible(context, MyValues.class, values, null);
// then
assertTrue("Access to enums is blocked!", actual);
assertFalse("Access to enums is allowed!", actual);
}
@Test
@@ -853,9 +857,11 @@ public class SecurityMemberAccessTest {
assertTrue("package java.lang. is accessible!", actual);
}
/**
* Test that the allowlist is enforced correctly for classes.
*/
@Test
public void classInclusion() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
TestBean2 bean = new TestBean2();
@@ -868,6 +874,9 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
/**
* Test that the allowlist is enforced correctly for packages.
*/
@Test
public void packageInclusion() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -882,6 +891,9 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
/**
* Test that the allowlist doesn't allow inherited methods unless the declaring class is also allowlisted.
*/
@Test
public void classInclusion_subclass() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -893,6 +905,9 @@ public class SecurityMemberAccessTest {
assertFalse(sma.checkAllowlist(bean, method));
}
/**
* Test that the allowlist allows inherited methods when both the target and declaring class are allowlisted.
*/
@Test
public void classInclusion_subclass_both() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -904,6 +919,10 @@ public class SecurityMemberAccessTest {
assertTrue(sma.checkAllowlist(bean, method));
}
/**
* Test that the allowlist doesn't allow inherited methods unless the package of the declaring class is also
* allowlisted.
*/
@Test
public void packageInclusion_subclass() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -915,6 +934,37 @@ public class SecurityMemberAccessTest {
assertFalse(sma.checkAllowlist(bean, method));
}
/**
* When the allowlist is enabled and proxy object access is disallowed, Hibernate proxies should not be allowed.
*/
@Test
public void classInclusion_hibernateProxy_disallowProxyObjectAccess() throws Exception {
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useAllowlistClasses(FooBar.class.getName());
assertFalse(sma.checkAllowlist(proxyObject, proxyMethod));
}
/**
* When the allowlist is enabled and proxy object access is allowed, Hibernate proxies should be allowlisted based
* on their underlying target object. Class allowlisting should work as expected.
*/
@Test
public void classInclusion_hibernateProxy_allowProxyObjectAccess() throws Exception {
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useAllowlistClasses(FooBar.class.getName());
assertTrue(sma.checkAllowlist(proxyObject, proxyMethod));
}
@Test
public void packageInclusion_subclass_both() throws Exception {
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
@@ -931,6 +981,15 @@ public class SecurityMemberAccessTest {
private static String formGetterName(String propertyName) {
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
}
@SuppressWarnings("unchecked")
private static <T> T mockHibernateProxy(T originalObject, Class<T> proxyInterface) {
return (T) Proxy.newProxyInstance(
proxyInterface.getClassLoader(),
new Class<?>[]{proxyInterface, HibernateProxy.class},
new DummyHibernateProxyHandler(originalObject)
);
}
}
class FooBar implements FooBarInterface {
@@ -1042,10 +1101,28 @@ class StaticTester {
}
protected static Field getFieldByName(String fieldName) throws NoSuchFieldException {
if (fieldName != null && fieldName.length() > 0) {
if (fieldName != null && !fieldName.isEmpty()) {
return StaticTester.class.getDeclaredField(fieldName);
} else {
throw new NoSuchFieldException("field: " + fieldName + " does not exist");
}
}
}
class DummyHibernateProxyHandler implements InvocationHandler {
private final Object instance;
public DummyHibernateProxyHandler(Object instance) {
this.instance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (HibernateProxy.class.getMethod("getHibernateLazyInitializer").equals(method)) {
LazyInitializer initializer = mock(LazyInitializer.class);
when(initializer.getImplementation()).thenReturn(instance);
return initializer;
}
return method.invoke(instance, args);
}
}
@@ -18,161 +18,167 @@
*/
package com.opensymphony.xwork2.validator;
import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.Mock;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.SimpleAction;
import com.opensymphony.xwork2.StubValueStack;
import com.opensymphony.xwork2.TestBean;
import com.opensymphony.xwork2.ValidationOrderAction;
import com.opensymphony.xwork2.XWorkTestCase;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.test.DataAware2;
import com.opensymphony.xwork2.test.SimpleAction2;
import com.opensymphony.xwork2.test.SimpleAction3;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
import com.opensymphony.xwork2.test.User;
import com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator;
import com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator;
import com.opensymphony.xwork2.validator.validators.ExpressionValidator;
import com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator;
import com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator;
import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator;
import com.opensymphony.xwork2.validator.validators.RequiredStringValidator;
import com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator;
import org.apache.struts2.StrutsException;
import org.xml.sax.SAXParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* DefaultActionValidatorManagerTest
*
* @author Jason Carreira
* @author tm_jee
* @version $Date$ $Id$
*/
public class DefaultActionValidatorManagerTest extends XWorkTestCase {
protected final String alias = "validationAlias";
DefaultActionValidatorManager actionValidatorManager;
Mock mockValidatorFileParser;
Mock mockValidatorFactory;
ValueStack stubValueStack;
@Override
protected void setUp() throws Exception {
actionValidatorManager = new DefaultActionValidatorManager();
super.setUp();
mockValidatorFileParser = new Mock(ValidatorFileParser.class);
actionValidatorManager.setValidatorFileParser((ValidatorFileParser)mockValidatorFileParser.proxy());
mockValidatorFactory = new Mock(ValidatorFactory.class);
actionValidatorManager.setValidatorFactory((ValidatorFactory)mockValidatorFactory.proxy());
stubValueStack = new StubValueStack();
ActionContext.of()
.withValueStack(stubValueStack)
.bind();
DefaultFileManagerFactory factory = new DefaultFileManagerFactory();
factory.setContainer(container);
factory.setFileManager(new DefaultFileManager());
actionValidatorManager.setFileManagerFactory(factory);
actionValidatorManager = container.inject(DefaultActionValidatorManager.class);
}
@Override
protected void tearDown() throws Exception {
actionValidatorManager = null;
super.tearDown();
mockValidatorFactory = null;
mockValidatorFileParser = null;
actionValidatorManager = null;
}
public void testBuildValidatorKey() {
String validatorKey = actionValidatorManager.buildValidatorKey(SimpleAction.class, alias);
assertEquals(SimpleAction.class.getName() + "/" + alias, validatorKey);
}
public void testBuildsValidatorsForAlias() {
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
new ArrayList());
actionValidatorManager.getValidators(SimpleAction.class, alias);
mockValidatorFileParser.verify();
List<Validator> validators = actionValidatorManager.getValidators(SimpleAction.class, alias);
assertThat(validators).hasSize(11).map(Validator::getClass).containsExactly(
ExpressionValidator.class,
RequiredFieldValidator.class,
IntRangeFieldValidator.class,
DoubleRangeFieldValidator.class,
DateRangeFieldValidator.class,
IntRangeFieldValidator.class,
IntRangeFieldValidator.class,
LongRangeFieldValidator.class,
ShortRangeFieldValidator.class,
RequiredFieldValidator.class,
IntRangeFieldValidator.class
);
assertThat(validators).hasSize(11).map(Validator::getDefaultMessage).containsExactly(
"Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}.",
"You must enter a value for bar.",
"bar must be between ${min} and ${max}, current value is ${bar}.",
"percentage must be between ${minExclusive} and ${maxExclusive}, current value is ${percentage}.",
"The date must be between 12-22-2002 and 12-25-2002.",
"Could not find foo.range!",
"Could not find baz.range!",
"Could not find foo.range!",
"Could not find foo.range!",
"You must enter a value for baz.",
"baz out of range."
);
}
public void testBuildsValidatorsForAliasError() {
boolean pass = false;
try {
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndThrow("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-badtest-validation.xml")),
new ConfigurationException());
List validatorList = actionValidatorManager.getValidators(TestBean.class, "badtest");
} catch (StrutsException ex) {
pass = true;
}
mockValidatorFileParser.verify();
assertTrue("Didn't throw exception on load failure", pass);
assertThatThrownBy(() -> actionValidatorManager.getValidators(TestBean.class, "badtest"))
.isInstanceOf(StrutsException.class)
.hasCause(new SAXParseException("Attribute \"foo\" must be declared for element type \"field-validator\".", null));
}
public void testGetValidatorsForInterface() {
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware2-validation.xml")),
new ArrayList());
actionValidatorManager.getValidators(DataAware2.class, alias);
mockValidatorFileParser.verify();
List<Validator> validators = actionValidatorManager.getValidators(DataAware2.class, alias);
assertThat(validators).hasSize(3).map(Validator::getClass).containsExactly(
RequiredFieldValidator.class,
RequiredStringValidator.class,
RequiredStringValidator.class
);
assertThat(validators).hasSize(3).map(Validator::getValidatorType).containsExactly(
"required",
"requiredstring",
"requiredstring"
);
assertThat(validators).hasSize(3).map(Validator::getDefaultMessage).containsExactly(
"You must enter a value for data.",
"You must enter a value for data.",
"You must enter a value for data."
);
}
public void testGetValidatorsFromInterface() {
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")),
new ArrayList());
actionValidatorManager.getValidators(SimpleAction3.class, alias);
mockValidatorFileParser.verify();
}
List<Validator> validators = actionValidatorManager.getValidators(SimpleAction3.class, alias);
public void testSameAliasWithDifferentClass() {
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validation.xml")),
new ArrayList());
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml")),
new ArrayList());
actionValidatorManager.getValidators(SimpleAction.class, alias);
actionValidatorManager.getValidators(SimpleAction2.class, alias);
mockValidatorFileParser.verify();
assertThat(validators).hasSize(13).map(Validator::getClass).containsExactly(
ExpressionValidator.class,
RequiredFieldValidator.class,
IntRangeFieldValidator.class,
DoubleRangeFieldValidator.class,
DateRangeFieldValidator.class,
IntRangeFieldValidator.class,
IntRangeFieldValidator.class,
LongRangeFieldValidator.class,
ShortRangeFieldValidator.class,
RequiredFieldValidator.class,
IntRangeFieldValidator.class,
RequiredFieldValidator.class,
RequiredStringValidator.class
);
assertThat(validators).hasSize(13).map(Validator::getValidatorType).containsExactly(
"expression",
"required",
"int",
"double",
"date",
"int",
"int",
"long",
"short",
"required",
"int",
"required",
"requiredstring"
);
assertThat(validators).hasSize(13).map(Validator::getDefaultMessage).containsExactly(
"Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}.",
"You must enter a value for bar.",
"bar must be between ${min} and ${max}, current value is ${bar}.",
"percentage must be between ${minExclusive} and ${maxExclusive}, current value is ${percentage}.",
"The date must be between 12-22-2002 and 12-25-2002.",
"Could not find foo.range!",
"Could not find baz.range!",
"Could not find foo.range!",
"Could not find foo.range!",
"You must enter a value for baz.",
"baz out of range.",
"You must enter a value for data.",
"You must enter a value for data."
);
}
/**
* Test to verify WW-3850.
*
* @since 2.3.5
*/
public void testBuildsValidatorsForClassError() {
// for this test we need to have a file manager with reloadingConfigs to true
@@ -188,12 +194,8 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
}
}
/*
// TODO: this all need to be converted to real unit tests
public void testSkipUserMarkerActionLevelShortCircuit() {
// get validators
List validatorList = actionValidatorManager.getValidators(User.class, null);
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
assertEquals(10, validatorList.size());
try {
@@ -202,16 +204,17 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
user.setEmail("bad_email");
user.setEmail2("bad_email");
ValidatorContext context = new GenericValidatorContext(user);
ValidationAware validationAware = new SimpleAction();
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
actionValidatorManager.validate(user, null, context);
assertTrue(context.hasFieldErrors());
// check field errors
List l = (List) context.getFieldErrors().get("email");
List<String> l = context.getFieldErrors().get("email");
assertNotNull(l);
assertEquals(1, l.size());
assertEquals("Not a valid e-mail.", l.get(0));
l = (List) context.getFieldErrors().get("email2");
l = context.getFieldErrors().get("email2");
assertNotNull(l);
assertEquals(2, l.size());
assertEquals("Not a valid e-mail2.", l.get(0));
@@ -219,19 +222,17 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
// check action errors
assertTrue(context.hasActionErrors());
l = (List) context.getActionErrors();
l = new ArrayList<>(context.getActionErrors());
assertNotNull(l);
assertEquals(2, l.size()); // both expression test failed see User-validation.xml
assertEquals("Email does not start with mark", l.get(0));
} catch (ValidationException ex) {
ex.printStackTrace();
fail("Validation error: " + ex.getMessage());
}
}
public void testSkipAllActionLevelShortCircuit2() {
// get validators
List validatorList = actionValidatorManager.getValidators(User.class, null);
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
assertEquals(10, validatorList.size());
try {
@@ -244,34 +245,30 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
user.setEmail("mark_bad_email_for_field_val@foo.com");
user.setEmail2("mark_bad_email_for_field_val@foo.com");
ValidatorContext context = new GenericValidatorContext(user);
ValidationAware validationAware = new SimpleAction();
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
actionValidatorManager.validate(user, null, context);
assertTrue(context.hasFieldErrors());
// check field errors
// we have an error in this field level, email does not ends with mycompany.com
List l = (List) context.getFieldErrors().get("email");
List<String> l = context.getFieldErrors().get("email");
assertNotNull(l);
assertEquals(1, l.size()); // because email-field-val is short-circuit
assertEquals("Email not from the right company.", l.get(0));
// check action errors
l = (List) context.getActionErrors();
l = new ArrayList<>(context.getActionErrors());
assertFalse(context.hasActionErrors());
assertEquals(0, l.size());
} catch (ValidationException ex) {
ex.printStackTrace();
fail("Validation error: " + ex.getMessage());
}
}
public void testActionLevelShortCircuit() throws Exception {
List validatorList = actionValidatorManager.getValidators(User.class, null);
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
assertEquals(10, validatorList.size());
User user = new User();
@@ -280,18 +277,18 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
user.setEmail("tmjee(at)yahoo.co.uk");
user.setEmail("tm_jee(at)yahoo.co.uk");
ValidatorContext context = new GenericValidatorContext(user);
ValidationAware validationAware = new SimpleAction();
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
actionValidatorManager.validate(user, null, context);
// check field level errors
// check field level errors
// shouldn't have any because action error prevents validation of anything else
List l = (List) context.getFieldErrors().get("email2");
List<String> l = context.getFieldErrors().get("email2");
assertNull(l);
// check action errors
assertTrue(context.hasActionErrors());
l = (List) context.getActionErrors();
l = new ArrayList<>(context.getActionErrors());
assertNotNull(l);
// we only get one, because UserMarker-validation.xml action-level validator
// already sc it :-)
@@ -299,10 +296,8 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
assertEquals("Email not the same as email2", l.get(0));
}
public void testShortCircuitNoErrors() {
// get validators
List validatorList = actionValidatorManager.getValidators(User.class, null);
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
assertEquals(10, validatorList.size());
try {
@@ -311,73 +306,72 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
user.setEmail("mark@mycompany.com");
user.setEmail2("mark@mycompany.com");
ValidatorContext context = new GenericValidatorContext(user);
ValidationAware validationAware = new SimpleAction();
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
actionValidatorManager.validate(user, null, context);
assertFalse(context.hasErrors());
} catch (ValidationException ex) {
ex.printStackTrace();
fail("Validation error: " + ex.getMessage());
}
}
public void testFieldErrorsOrder() throws Exception {
ValidationOrderAction action = new ValidationOrderAction();
actionValidatorManager.validate(action, "actionContext");
Map fieldErrors = action.getFieldErrors();
Iterator i = fieldErrors.entrySet().iterator();
ValidationOrderAction action = new ValidationOrderAction();
actionValidatorManager.validate(action, "actionContext");
Map<String, List<String>> fieldErrors = action.getFieldErrors();
Iterator<Map.Entry<String, List<String>>> i = fieldErrors.entrySet().iterator();
assertNotNull(fieldErrors);
assertEquals(fieldErrors.size(), 12);
assertNotNull(fieldErrors);
assertEquals(fieldErrors.size(), 12);
Map.Entry e = (Map.Entry) i.next();
assertEquals(e.getKey(), "username");
assertEquals(((List)e.getValue()).get(0), "username required");
Map.Entry<String, List<String>> e = i.next();
assertEquals(e.getKey(), "username");
assertEquals(e.getValue().get(0), "username required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "password");
assertEquals(((List)e.getValue()).get(0), "password required");
e = i.next();
assertEquals(e.getKey(), "password");
assertEquals((e.getValue()).get(0), "password required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "confirmPassword");
assertEquals(((List)e.getValue()).get(0), "confirm password required");
e = i.next();
assertEquals(e.getKey(), "confirmPassword");
assertEquals((e.getValue()).get(0), "confirm password required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "firstName");
assertEquals(((List)e.getValue()).get(0), "first name required");
e = i.next();
assertEquals(e.getKey(), "firstName");
assertEquals((e.getValue()).get(0), "first name required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "lastName");
assertEquals(((List)e.getValue()).get(0), "last name required");
e = i.next();
assertEquals(e.getKey(), "lastName");
assertEquals((e.getValue()).get(0), "last name required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "city");
assertEquals(((List)e.getValue()).get(0), "city is required");
e = i.next();
assertEquals(e.getKey(), "city");
assertEquals((e.getValue()).get(0), "city is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "province");
assertEquals(((List)e.getValue()).get(0), "province is required");
e = i.next();
assertEquals(e.getKey(), "province");
assertEquals((e.getValue()).get(0), "province is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "country");
assertEquals(((List)e.getValue()).get(0), "country is required");
e = i.next();
assertEquals(e.getKey(), "country");
assertEquals((e.getValue()).get(0), "country is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "postalCode");
assertEquals(((List)e.getValue()).get(0), "postal code is required");
e = i.next();
assertEquals(e.getKey(), "postalCode");
assertEquals((e.getValue()).get(0), "postal code is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "email");
assertEquals(((List)e.getValue()).get(0), "email is required");
e = i.next();
assertEquals(e.getKey(), "email");
assertEquals((e.getValue()).get(0), "email is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "website");
assertEquals(((List)e.getValue()).get(0), "website is required");
e = (Map.Entry) i.next();
assertEquals(e.getKey(), "passwordHint");
assertEquals(((List)e.getValue()).get(0), "password hint is required");
e = i.next();
assertEquals(e.getKey(), "website");
assertEquals((e.getValue()).get(0), "website is required");
e = i.next();
assertEquals(e.getKey(), "passwordHint");
assertEquals((e.getValue()).get(0), "password hint is required");
}
*/
}
@@ -28,6 +28,8 @@ import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.conversion.impl.ConversionData;
import org.easymock.EasyMock;
import java.sql.Date;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
@@ -142,6 +144,15 @@ public class VisitorFieldValidatorTest extends XWorkTestCase {
assertEquals(1, errors.size());
}
public void testDateValidation() throws Exception {
action.setBirthday(Date.valueOf(LocalDate.now().minusYears(20)));
action.setContext("birthday");
validate("birthday");
assertFalse(action.hasFieldErrors());
}
public void testContextIsOverriddenByContextParamInValidationXML() throws Exception {
validate("visitorValidationAlias");
assertTrue(action.hasFieldErrors());
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.TestBean;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -37,7 +38,7 @@ public class VisitorValidatorTestAction extends ActionSupport {
private String context;
private TestBean bean = new TestBean();
private TestBean[] testBeanArray;
private Date birthday;
public VisitorValidatorTestAction() {
testBeanArray = new TestBean[5];
@@ -80,4 +81,12 @@ public class VisitorValidatorTestAction extends ActionSupport {
public List<TestBean> getTestBeanList() {
return testBeanList;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
@@ -25,6 +25,7 @@ import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
import com.opensymphony.xwork2.validator.annotations.Validations;
import com.opensymphony.xwork2.validator.annotations.ValidatorType;
import org.apache.struts2.interceptor.parameter.StrutsParameter;
import org.apache.struts2.views.jsp.ui.User;
import java.util.Arrays;
@@ -92,6 +93,7 @@ public class TestAction extends ActionSupport {
return collection;
}
@StrutsParameter
public void setCollection(Collection collection) {
this.collection = collection;
}
@@ -100,6 +102,7 @@ public class TestAction extends ActionSupport {
return map;
}
@StrutsParameter
public void setMap(Map map) {
this.map = map;
}
@@ -108,6 +111,7 @@ public class TestAction extends ActionSupport {
return foo;
}
@StrutsParameter
public void setFoo(String foo) {
this.foo = foo;
}
@@ -116,6 +120,7 @@ public class TestAction extends ActionSupport {
return result;
}
@StrutsParameter
public void setResult(String result) {
this.result = result;
}
@@ -124,6 +129,7 @@ public class TestAction extends ActionSupport {
return user;
}
@StrutsParameter
public void setUser(User user) {
this.user = user;
}
@@ -132,6 +138,7 @@ public class TestAction extends ActionSupport {
return array;
}
@StrutsParameter
public void setArray(String[] array) {
this.array = array;
}
@@ -140,6 +147,7 @@ public class TestAction extends ActionSupport {
return objectArray;
}
@StrutsParameter
public void setObjectArray(Object[] arrayObject) {
this.objectArray = arrayObject;
}
@@ -148,6 +156,7 @@ public class TestAction extends ActionSupport {
return list;
}
@StrutsParameter
public void setList(String[][] list) {
this.list = list;
}
@@ -156,10 +165,12 @@ public class TestAction extends ActionSupport {
return list2;
}
@StrutsParameter
public void setList2(List list2) {
this.list2 = list2;
}
@StrutsParameter
public void setList3(List list) {
this.list3 = list;
}
@@ -172,6 +183,7 @@ public class TestAction extends ActionSupport {
return this.collection2;
}
@StrutsParameter
public void setCollection2(Collection collection) {
this.collection2 = collection;
}
@@ -180,6 +192,7 @@ public class TestAction extends ActionSupport {
return fooInt;
}
@StrutsParameter
public void setFooInt(Integer fooInt) {
this.fooInt = fooInt;
}
@@ -225,6 +238,7 @@ public class TestAction extends ActionSupport {
return status;
}
@StrutsParameter
public void setStatus(SomeEnum status) {
this.status = status;
}
@@ -237,6 +251,7 @@ public class TestAction extends ActionSupport {
return floatNumber;
}
@StrutsParameter
public void setFloatNumber(Float floatNumber) {
this.floatNumber = floatNumber;
}
@@ -245,6 +260,7 @@ public class TestAction extends ActionSupport {
return id;
}
@StrutsParameter
public void setId(Long id) {
this.id = id;
}
@@ -253,6 +269,7 @@ public class TestAction extends ActionSupport {
return enumList;
}
@StrutsParameter
public void setEnumList(List<SomeEnum> enumList) {
this.enumList = enumList;
}
@@ -261,6 +278,7 @@ public class TestAction extends ActionSupport {
return intList;
}
@StrutsParameter
public void setIntList(List<Integer> intList) {
this.intList = intList;
}
@@ -269,6 +287,7 @@ public class TestAction extends ActionSupport {
return someBool;
}
@StrutsParameter
public void setSomeBool(Boolean someBool) {
this.someBool = someBool;
}
@@ -55,10 +55,12 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -589,6 +591,30 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase {
assertEquals(Locale.CANADA_FRENCH, dispatcher.getLocale(request));
}
@Test
public void testExcludePatterns() {
initDispatcher(singletonMap(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json"));
assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
"/ns1/.*\\.json",
"/ns2/.*\\.json"
);
}
@Test
public void testExcludePatternsUsingCustomSeparator() {
Map<String, String> props = new HashMap<>();
props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
initDispatcher(props);
assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
"/ns1/[a-z]{1,10}.json",
"/ns2/[a-z]{1,10}.json"
);
}
public static Dispatcher spyDispatcherWithConfigurationManager(Dispatcher dispatcher, ConfigurationManager configurationManager) {
Dispatcher spiedDispatcher = spy(dispatcher);
doReturn(configurationManager).when(spiedDispatcher).createConfigurationManager(any());
@@ -1,86 +0,0 @@
/*
* 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;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.config.PropertiesConfigurationProvider;
import java.util.List;
import java.util.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class InitOperationsTest extends StrutsInternalTestCase {
public void testExcludePatterns() {
// given
loadConfigurationProviders(new PropertiesConfigurationProvider() {
@Override
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json");
}
});
Dispatcher mockDispatcher = mock(Dispatcher.class);
when(mockDispatcher.getContainer()).thenReturn(container);
// when
InitOperations init = new InitOperations();
List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
// then
assertThat(patterns).extracting(Pattern::toString).containsOnly(
"/ns1/.*\\.json",
"/ns2/.*\\.json"
);
}
public void testExcludePatternsUsingCustomSeparator() {
// given
loadConfigurationProviders(new PropertiesConfigurationProvider() {
@Override
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
}
});
Dispatcher mockDispatcher = mock(Dispatcher.class);
when(mockDispatcher.getContainer()).thenReturn(container);
// when
InitOperations init = new InitOperations();
String separator = container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
// then
assertThat(separator).isNotBlank().isEqualTo("//");
assertThat(patterns).extracting(Pattern::toString).containsOnly(
"/ns1/[a-z]{1,10}.json",
"/ns2/[a-z]{1,10}.json"
);
}
}
@@ -26,13 +26,10 @@ import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -127,6 +124,7 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig();
filterConfig.addInitParameter("struts.action.excludePattern", ".*hello.*");
MockFilterChain filterChain = new MockFilterChain() {
@Override
public void doFilter(ServletRequest req, ServletResponse res) {
@@ -135,14 +133,7 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest {
};
request.setRequestURI("/hello.action");
StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter() {
@Override
public void init( FilterConfig filterConfig ) throws ServletException {
super.init(filterConfig);
excludedPatterns = new ArrayList<>();
excludedPatterns.add(Pattern.compile(".*hello.*"));
}
};
StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter();
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
assertEquals(200, response.getStatus());
@@ -93,7 +93,7 @@ abstract class AbstractMultiPartRequestTest {
.isEmpty();
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsOnly("file1", "file2");
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
assertThat(file.isFile())
@@ -142,7 +142,7 @@ abstract class AbstractMultiPartRequestTest {
.isEmpty();
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsOnly("file1");
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
if (Objects.equals(file.getName(), "test1.csv")) {
@@ -193,7 +193,7 @@ abstract class AbstractMultiPartRequestTest {
.isEmpty();
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsOnly("file1", "file2");
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
assertThat(file.isFile())
@@ -240,7 +240,7 @@ abstract class AbstractMultiPartRequestTest {
.isEmpty();
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsOnly("file1", "file2");
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
assertThat(file.isFile())
@@ -306,7 +306,7 @@ abstract class AbstractMultiPartRequestTest {
.containsExactly("struts.messages.upload.error.FileUploadContentTypeException");
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.isEmpty();
}
@@ -410,7 +410,7 @@ abstract class AbstractMultiPartRequestTest {
.isEmpty();
assertThat(multiPart.getFileParameterNames().asIterator()).toIterable()
.asList()
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsOnly("file1");
assertThat(multiPart.getFile("file1")).allSatisfy(file -> {
assertThat(file.isFile())
@@ -458,6 +458,8 @@ abstract class AbstractMultiPartRequestTest {
.isEqualTo("short text");
assertThat(multiPart.getParameterValues("multi"))
.containsOnly("multi1", "multi2");
assertThat(multiPart.getParameterValues("not-existing"))
.isNull();
}
@Test
@@ -25,11 +25,12 @@ import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
import com.opensymphony.xwork2.security.DefaultExcludedPatternsChecker;
import jakarta.servlet.http.Cookie;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsInternalTestCase;
import org.apache.struts2.action.CookiesAware;
import org.springframework.mock.web.MockHttpServletRequest;
import jakarta.servlet.http.Cookie;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -451,49 +452,6 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
assertFalse(excludedName.get(reqCookieName));
}
public void testActionCookieAwareWithStrutsInternalsAccess() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
String sessionCookieName = "session.userId";
String sessionCookieValue = "session.userId=1";
String appCookieName = "application.userId";
String appCookieValue = "application.userId=1";
String reqCookieName = "request.userId";
String reqCookieValue = "request.userId=1";
request.setCookies(
new Cookie(sessionCookieName, "1"),
new Cookie("1", sessionCookieValue),
new Cookie(appCookieName, "1"),
new Cookie("1", appCookieValue),
new Cookie(reqCookieName, "1"),
new Cookie("1", reqCookieValue)
);
ServletActionContext.setRequest(request);
final Map<String, Boolean> excludedName = new HashMap<>();
CookieInterceptor interceptor = new CookieInterceptor() {
@Override
protected boolean isAcceptableName(String name) {
boolean accepted = super.isAcceptableName(name);
excludedName.put(name, accepted);
return accepted;
}
};
interceptor.setExcludedPatternsChecker(new DefaultExcludedPatternsChecker());
interceptor.setAcceptedPatternsChecker(new DefaultAcceptedPatternsChecker());
interceptor.setCookiesName("*");
MockActionInvocation invocation = new MockActionInvocation();
invocation.setAction(new MockActionWithActionCookieAware());
interceptor.intercept(invocation);
assertFalse(excludedName.get(sessionCookieName));
assertFalse(excludedName.get(appCookieName));
assertFalse(excludedName.get(reqCookieName));
}
public static class MockActionWithCookieAware extends ActionSupport implements CookiesAware {
private static final long serialVersionUID = -6202290616812813386L;
@@ -503,46 +461,7 @@ public class CookieInterceptorTest extends StrutsInternalTestCase {
private String cookie2;
private String cookie3;
public void setCookiesMap(Map<String, String> cookies) {
this.cookies = cookies;
}
public Map getCookiesMap() {
return this.cookies;
}
public String getCookie1() {
return cookie1;
}
public void setCookie1(String cookie1) {
this.cookie1 = cookie1;
}
public String getCookie2() {
return cookie2;
}
public void setCookie2(String cookie2) {
this.cookie2 = cookie2;
}
public String getCookie3() {
return cookie3;
}
public void setCookie3(String cookie3) {
this.cookie3 = cookie3;
}
}
public static class MockActionWithActionCookieAware extends ActionSupport implements org.apache.struts2.action.CookiesAware {
private Map cookies = Collections.EMPTY_MAP;
private String cookie1;
private String cookie2;
private String cookie3;
@Override
public void withCookies(Map<String, String> cookies) {
this.cookies = cookies;
}
@@ -19,6 +19,7 @@
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import org.apache.logging.log4j.util.Strings;
import org.apache.struts2.StrutsInternalTestCase;
@@ -32,6 +33,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import static org.junit.Assert.assertNotEquals;
@@ -74,8 +76,10 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
public void testEnforcingCspHeadersSet() throws Exception {
String reportUri = "/csp-reports";
String reportTo = "csp-group";
boolean enforcingMode = true;
interceptor.setReportUri(reportUri);
interceptor.setReportTo(reportTo);
interceptor.setEnforcingMode(enforcingMode);
session.setAttribute("nonce", "foo");
@@ -84,13 +88,15 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
assertNotNull("Nonce key does not exist", session.getAttribute("nonce"));
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.getAttribute("nonce")));
assertNotEquals("New nonce value couldn't be set", "foo", session.getAttribute("nonce"));
checkHeader(reportUri, enforcingMode);
checkHeader(reportUri, reportTo, enforcingMode);
}
public void testReportingCspHeadersSet() throws Exception {
String reportUri = "/csp-reports";
String reportTo = "csp-group";
boolean enforcingMode = false;
interceptor.setReportUri(reportUri);
interceptor.setReportTo(reportTo);
interceptor.setEnforcingMode(enforcingMode);
session.setAttribute("nonce", "foo");
@@ -98,7 +104,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
assertNotNull("Nonce value is empty", session.getAttribute("nonce"));
assertNotEquals("New nonce value couldn't be set", "foo", session.getAttribute("nonce"));
checkHeader(reportUri, enforcingMode);
checkHeader(reportUri, reportTo, enforcingMode);
}
public void test_uriSetOnlyWhenSetIsCalled() throws Exception {
@@ -174,21 +180,89 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
checkHeader("/report-uri", enforcingMode);
}
public void testNonExistingCspSettingsClassName() throws Exception {
boolean enforcingMode = true;
mai.setAction(new TestAction());
request.setContextPath("/app");
interceptor.setEnforcingMode(enforcingMode);
interceptor.setReportUri("/report-uri");
interceptor.setPrependServletContext(false);
try {
interceptor.setCspSettingsClassName("foo");
interceptor.intercept(mai);
fail("Expected exception");
} catch (ConfigurationException e) {
assertEquals("The class foo doesn't exist!", e.getMessage());
}
}
public void testInvalidCspSettingsClassName() throws Exception {
boolean enforcingMode = true;
mai.setAction(new TestAction());
request.setContextPath("/app");
interceptor.setEnforcingMode(enforcingMode);
interceptor.setReportUri("/report-uri");
interceptor.setPrependServletContext(false);
try {
interceptor.setCspSettingsClassName(Integer.class.getName());
interceptor.intercept(mai);
fail("Expected exception");
} catch (ConfigurationException e) {
assertEquals("The class java.lang.Integer doesn't implement org.apache.struts2.interceptor.csp.CspSettings!", e.getMessage());
}
}
public void testCustomCspSettingsClassName() throws Exception {
boolean enforcingMode = true;
mai.setAction(new TestAction());
request.setContextPath("/app");
interceptor.setEnforcingMode(enforcingMode);
interceptor.setReportUri("/report-uri");
interceptor.setPrependServletContext(false);
interceptor.setCspSettingsClassName(CustomDefaultCspSettings.class.getName());
interceptor.intercept(mai);
String header = response.getHeader(CspSettings.CSP_ENFORCE_HEADER);
// no other customization matters for this particular class
assertEquals("foo", header);
}
public void checkHeader(String reportUri, boolean enforcingMode) {
checkHeader(reportUri, null, enforcingMode);
}
public void checkHeader(String reportUri, String reportTo, boolean enforcingMode) {
String expectedCspHeader;
if (Strings.isEmpty(reportUri)) {
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; ",
CspSettings.OBJECT_SRC, CspSettings.NONE,
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
CspSettings.BASE_URI, CspSettings.NONE
CspSettings.OBJECT_SRC, CspSettings.NONE,
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
CspSettings.BASE_URI, CspSettings.NONE
);
} else {
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s",
CspSettings.OBJECT_SRC, CspSettings.NONE,
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
CspSettings.BASE_URI, CspSettings.NONE,
CspSettings.REPORT_URI, reportUri
);
if (Strings.isEmpty(reportTo)) {
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s; ",
CspSettings.OBJECT_SRC, CspSettings.NONE,
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
CspSettings.BASE_URI, CspSettings.NONE,
CspSettings.REPORT_URI, reportUri
);
} else {
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s; %s %s; ",
CspSettings.OBJECT_SRC, CspSettings.NONE,
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
CspSettings.BASE_URI, CspSettings.NONE,
CspSettings.REPORT_URI, reportUri,
CspSettings.REPORT_TO, reportTo
);
}
}
String header;
@@ -207,10 +281,11 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
super.setUp();
container.inject(interceptor);
ActionContext context = ActionContext.getContext()
.withServletRequest(request)
.withServletResponse(response)
.withSession(new SessionMap(request))
.bind();
.withContainer(container)
.withServletRequest(request)
.withServletResponse(response)
.withSession(new SessionMap(request))
.bind();
mai.setInvocationContext(context);
session = request.getSession();
}
@@ -230,4 +305,15 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
return settings;
}
}
/**
* Custom DefaultCspSettings class that overrides the createPolicyFormat method
* to return a fixed value.
*/
public static class CustomDefaultCspSettings extends DefaultCspSettings {
protected String createPolicyFormat(HttpServletRequest request) {
return "foo";
}
}
}
@@ -147,6 +147,26 @@ public class I18nInterceptorTest extends TestCase {
assertEquals(Locale.getDefault(), session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
}
public void testTrimableLocaleString1() throws Exception {
prepare(I18nInterceptor.DEFAULT_PARAMETER, "de\n");
interceptor.intercept(mai);
assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
}
public void testTrimableLocaleString2() throws Exception {
prepare(I18nInterceptor.DEFAULT_PARAMETER, "de ");
interceptor.intercept(mai);
assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
}
public void testWithVariant() throws Exception {
prepare(I18nInterceptor.DEFAULT_PARAMETER, "ja_JP_JP");
interceptor.intercept(mai);
@@ -20,6 +20,7 @@ package org.apache.struts2.interceptor.exec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.SecureRandom;
@@ -42,6 +43,8 @@ import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import static org.awaitility.Awaitility.await;
/**
* Test case for BackgroundProcessTest.
*/
@@ -60,9 +63,9 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
invocation.setInvocationContext(ActionContext.getContext());
StrutsBackgroundProcess bp = (StrutsBackgroundProcess) new StrutsBackgroundProcess(
invocation,
"BackgroundProcessTest.testSerializeDeserialize",
Thread.MIN_PRIORITY
invocation,
"BackgroundProcessTest.testSerializeDeserialize",
Thread.MIN_PRIORITY
).prepare();
executor.execute(bp);
@@ -121,6 +124,31 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
assertEquals(100, mutableState.get());
}
public void testErrorableProcesses1() {
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> {
throw new IllegalStateException("boom");
});
BackgroundProcess bp = new ErrorableBackgroundProcess(invocation, null).prepare();
executor.execute(bp);
await().atLeast(100, TimeUnit.MILLISECONDS).until(bp::isDone);
assertTrue("afterInvocation not called in case of exception", ((ErrorableBackgroundProcess) bp).isDoneAfter());
}
public void testErrorableProcesses2() {
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> "done");
IllegalStateException expected = new IllegalStateException("after!");
BackgroundProcess bp = new ErrorableBackgroundProcess(invocation, expected).prepare();
executor.execute(bp);
await().atLeast(100, TimeUnit.MILLISECONDS).until(bp::isDone);
assertEquals(expected, bp.getException());
}
public void testUnpreparedProcess() throws ExecutionException, InterruptedException, TimeoutException {
// given
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> "done");
@@ -148,7 +176,8 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
}
private static class NotSerializableException extends Exception {
private MockHttpServletRequest notSerializableField;
@SuppressWarnings("unused")
private final MockHttpServletRequest notSerializableField;
NotSerializableException(MockHttpServletRequest notSerializableField) {
this.notSerializableField = notSerializableField;
@@ -171,10 +200,29 @@ class LockBackgroundProcess extends StrutsBackgroundProcess {
super.run();
}
}
}
class ErrorableBackgroundProcess extends StrutsBackgroundProcess {
private final Exception afterException;
private boolean doneAfter;
public ErrorableBackgroundProcess(ActionInvocation invocation, Exception afterException) {
super(invocation, "errorabale process", Thread.NORM_PRIORITY);
this.afterException = afterException;
}
@Override
protected void afterInvocation() throws Exception {
super.afterInvocation();
lock.notify();
if (afterException != null) {
throw afterException;
} else {
super.afterInvocation();
doneAfter = true;
}
}
public boolean isDoneAfter() {
return doneAfter;
}
}
@@ -36,6 +36,7 @@ import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import com.opensymphony.xwork2.ognl.OgnlValueStack;
import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
import com.opensymphony.xwork2.ognl.SecurityMemberAccess;
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.ognl.accessor.RootAccessor;
import com.opensymphony.xwork2.util.ValueStack;
@@ -90,7 +91,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("test%test", "test%test");
}
};
pi.setParameters(a, stack, HttpParameters.create(parameters).build());
pi.applyParameters(a, stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -113,18 +114,20 @@ public class ParametersInterceptorTest extends XWorkTestCase {
// when
ValidateAction action = new ValidateAction();
pi.setParameters(action, vs, HttpParameters.create(params).build());
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(3, action.getActionMessages().size());
assertEquals(3, action.getActionErrors().size());
String msg1 = action.getActionMessage(0);
String msg2 = action.getActionMessage(1);
String msg3 = action.getActionMessage(2);
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
assertEquals("Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
assertEquals("Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
assertEquals("Error setting expression 'top['name'](0)' with value 'true'", msg3);
String msg1 = actionErrors.get(0);
String msg2 = actionErrors.get(1);
String msg3 = actionErrors.get(2);
assertEquals("Unexpected Exception caught setting 'expression' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
assertEquals("Unexpected Exception caught setting 'name' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
assertEquals("Unexpected Exception caught setting 'top['name'](0)' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'top['name'](0)' with value 'true'", msg3);
assertNull(action.getName());
}
@@ -158,7 +161,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
// when
ValidateAction action = new ValidateAction();
pi.setParameters(action, vs, HttpParameters.create(params).build());
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(0, action.getActionMessages().size());
@@ -198,18 +201,19 @@ public class ParametersInterceptorTest extends XWorkTestCase {
// when
ValidateAction action = new ValidateAction();
pi.setParameters(action, vs, HttpParameters.create(params).build());
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(3, action.getActionMessages().size());
assertEquals(3, action.getActionErrors().size());
String msg1 = action.getActionMessage(0);
String msg2 = action.getActionMessage(1);
String msg3 = action.getActionMessage(2);
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
String msg1 = actionErrors.get(0);
String msg2 = actionErrors.get(1);
String msg3 = actionErrors.get(2);
assertEquals("Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
assertEquals("Unexpected Exception caught setting 'class.classLoader.defaultAssertionStatus' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
assertEquals("Unexpected Exception caught setting 'class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
assertEquals("Unexpected Exception caught setting 'model.class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
assertFalse(excluded.get(pollution1));
assertFalse(excluded.get(pollution2));
@@ -317,7 +321,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
// when
ValidateAction action = new ValidateAction();
pi.setParameters(action, vs, HttpParameters.create(params).build());
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(0, action.getActionMessages().size());
@@ -432,7 +436,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
parameters.put("huuhaa", "");
Action action = new SimpleAction();
parametersInterceptor.setParameters(action, stack, HttpParameters.create(parameters).build());
parametersInterceptor.applyParameters(action, stack, HttpParameters.create(parameters).build());
assertEquals(1, actual.size());
}
@@ -582,8 +586,8 @@ public class ParametersInterceptorTest extends XWorkTestCase {
container.inject(config.getInterceptors().get(0).getInterceptor());
ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap());
proxy.execute();
final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
final String actionError = "" + ((SimpleAction) proxy.getAction()).getActionErrors().toArray()[0];
assertTrue(actionError.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
}
public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception {
@@ -626,7 +630,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
parameters.put("user.name", "Superman");
Action action = new SimpleAction();
pi.setParameters(action, stack, HttpParameters.create(parameters).build());
pi.applyParameters(action, stack, HttpParameters.create(parameters).build());
assertEquals("ordered should be false by default", false, pi.isOrdered());
assertEquals(2, actual.size());
@@ -653,7 +657,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
parameters.put("user.name", "Superman");
Action action = new SimpleAction();
pi.setParameters(action, stack, HttpParameters.create(parameters).build());
pi.applyParameters(action, stack, HttpParameters.create(parameters).build());
assertEquals(true, pi.isOrdered());
assertEquals(3, actual.size());
@@ -693,7 +697,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("fooKey", "fooValue");
}
};
pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
pi.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -724,7 +728,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
};
// when
interceptor.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
interceptor.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
// then
assertEquals(expected, actual);
@@ -751,7 +755,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
};
// when
interceptor.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
interceptor.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
// then
assertEquals(expected, actual);
@@ -803,7 +807,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("fooKey2", "");
}
};
pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
pi.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -840,7 +844,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("fooKey2", "");
}
};
pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
pi.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -879,7 +883,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("fooKey2", "");
}
};
pi.setParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
pi.applyParameters(new NoParametersAction(), stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -923,7 +927,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
put("fooKey3", "");
}
};
pi.setParameters(a, stack, HttpParameters.create(parameters).build());
pi.applyParameters(a, stack, HttpParameters.create(parameters).build());
assertEquals(expected, actual);
}
@@ -961,7 +965,7 @@ public class ParametersInterceptorTest extends XWorkTestCase {
ValueStack stack = new OgnlValueStack(
container.getInstance(XWorkConverter.class),
(CompoundRootAccessor) container.getInstance(RootAccessor.class),
container.getInstance(TextProvider.class, "system"), true) {
container.getInstance(TextProvider.class, "system"), new SecurityMemberAccess(true)) {
@Override
public void setValue(String expr, Object value) {
actual.put(expr, value);
@@ -1014,59 +1018,65 @@ public class ParametersInterceptorTest extends XWorkTestCase {
class ValidateAction implements ValidationAware {
private final List<String> messages = new LinkedList<>();
private final List<String> errors = new LinkedList<>();
private String name;
@Override
public void setActionErrors(Collection<String> errorMessages) {
}
@Override
public Collection<String> getActionErrors() {
return null;
return errors;
}
@Override
public void setActionMessages(Collection<String> messages) {
}
@Override
public Collection<String> getActionMessages() {
return messages;
}
@Override
public void setFieldErrors(Map<String, List<String>> errorMap) {
}
@Override
public Map<String, List<String>> getFieldErrors() {
return null;
}
@Override
public void addActionError(String anErrorMessage) {
errors.add(anErrorMessage);
}
@Override
public void addActionMessage(String aMessage) {
messages.add(aMessage);
}
@Override
public void addFieldError(String fieldName, String errorMessage) {
}
@Override
public boolean hasActionErrors() {
return false;
return !errors.isEmpty();
}
@Override
public boolean hasActionMessages() {
return !messages.isEmpty();
}
public boolean hasErrors() {
return false;
}
@Override
public boolean hasFieldErrors() {
return false;
}
public String getActionMessage(int index) {
return messages.get(index);
}
public String getName() {
return name;
}
@@ -0,0 +1,240 @@
/*
* 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 com.opensymphony.xwork2.XWorkTestCase;
import com.opensymphony.xwork2.ognl.OgnlValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import ognl.OgnlRuntime;
import org.apache.struts2.StrutsConstants;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertNotEquals;
public class OgnlSetPossiblePropertyTest extends XWorkTestCase {
private OgnlValueStack vs;
public <T> T setUpClass(Class<T> holderClass) throws Exception {
Map<String, String> properties = new HashMap<>();
properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, holderClass.getName() + "$ExcludedField");
loadButSet(properties);
vs = (OgnlValueStack) container.getInstance(ValueStackFactory.class).createValueStack();
T nonExcludedHolder = holderClass.getDeclaredConstructor().newInstance();
vs.push(nonExcludedHolder);
return nonExcludedHolder;
}
public void testSetFieldValueDontAssignWhenHolderClassAndFieldClassHaveOnlyPublicFields() throws Exception {
/* Case: to test setFieldValue without having set method
*
* NonExcludedHolder class
* - field: public
* ExcludeField class
* - field: public
*/
HolderWithPublicField holder = setUpClass(HolderWithPublicField.class);
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
}
public void testSetMethodValueDontAssignWhenHolderAndFieldClassWithPublicMethodsAndPrivateFields() throws Exception {
/* Case: to test setMethodValue, so to make fields as private
*
* NonExcludedHolder class
* - field: private
* - method: public
* ExcludeField class
* - field: private
* - method: public
*/
HolderWithPublicMethod holder = setUpClass(HolderWithPublicMethod.class);
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
}
public void testSetFieldValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicField() throws Exception {
/* Case: to test setFieldValue when holder get method is public and field class set method is private so fallback to set field
*
* NonExcludedHolder class
* - field: private
* - method: public
* ExcludeField class
* - field: public
* - method: private
*/
HolderWhoseFieldWithPrivateMethod holder = setUpClass(HolderWhoseFieldWithPrivateMethod.class);
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
}
public void testSetMethodValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicMethod() throws Exception {
/* Case: to test setMethodValue when holder get method is public and field class field is private so only call to set method
*
* NonExcludedHolder class
* - field: private
* - method: public
* ExcludeField class
* - field: private
* - method: public
*/
HolderWhoseFieldWithPublicMethod holder = setUpClass(HolderWhoseFieldWithPublicMethod.class);
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
}
public void testWriteMethodValueDontAssignWhenWriteMethodIsNotAccessible() throws Exception {
/* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is private so fallback to write method
*
* NonExcludedHolder class
* - field: private
* - method: public
* ExcludeField class
* - field: private
* - set method: private
* - write method: public
*/
HolderWhoseFieldWithPublicWriteMethod holder = setUpClass(HolderWhoseFieldWithPublicWriteMethod.class);
Method writeMethod = OgnlRuntime.getWriteMethod(HolderWhoseFieldWithPublicWriteMethod.ExcludedField.class, "excludedFieldString");
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertEquals("setexcludedfieldstring", writeMethod.getName());
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
}
public void testWriteMethodValueDontAssignWhenPublicSetterDifferentFieldName() throws Exception {
/* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is of different name
*
* NonExcludedHolder class
* - field: private
* - method: public
* ExcludeField class
* - field: private
* - set method: public (but not matching with field name)
*/
HolderWhoseFieldWithPublicSetterDifferentFieldName holder = setUpClass(HolderWhoseFieldWithPublicSetterDifferentFieldName.class);
vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldStringInternal);
}
public static class HolderWithPublicField {
public ExcludedField excludedField = new ExcludedField();
public static class ExcludedField {
public String excludedFieldString = "defaultValue";
}
}
public static class HolderWithPublicMethod {
private ExcludedField excludedField = new ExcludedField();
public ExcludedField getExcludedField() {
return excludedField;
}
public static class ExcludedField {
private String excludedFieldString = "defaultValue";
public void setExcludedFieldString(String value) {
this.excludedFieldString = value;
}
}
}
public static class HolderWhoseFieldWithPrivateMethod {
private ExcludedField excludedField = new ExcludedField();
public ExcludedField getExcludedField() {
return excludedField;
}
public static class ExcludedField {
public String excludedFieldString = "defaultValue";
private void setExcludedFieldString(String value) {
this.excludedFieldString = value;
}
}
}
public static class HolderWhoseFieldWithPublicMethod {
private ExcludedField excludedField = new ExcludedField();
public ExcludedField getExcludedField() {
return excludedField;
}
public static class ExcludedField {
public String excludedFieldString = "defaultValue";
private void setExcludedFieldString(String value) {
this.excludedFieldString = value;
}
}
}
public static class HolderWhoseFieldWithPublicWriteMethod {
private ExcludedField excludedField = new ExcludedField();
public ExcludedField getExcludedField() {
return excludedField;
}
public static class ExcludedField {
private String excludedFieldString = "defaultValue";
private void setExcludedFieldString(String value) {
this.excludedFieldString = value;
}
public void setexcludedfieldstring(String value) {
this.excludedFieldString = value;
}
}
}
public static class HolderWhoseFieldWithPublicSetterDifferentFieldName {
private ExcludedField excludedField = new ExcludedField();
public ExcludedField getExcludedField() {
return excludedField;
}
public static class ExcludedField {
private String excludedFieldStringInternal = "defaultValue";
public void setExcludedFieldString(String value) {
this.excludedFieldStringInternal = value;
}
}
}
}
@@ -112,6 +112,14 @@ public class StrutsQueryStringParserTest {
assertEquals("test", queryParameters.getQueryFragment());
}
@Test
public void shouldHandleOnlyFragment() {
QueryStringParser.Result queryParameters = parser.parse("#test");
assertTrue(queryParameters.getQueryParams().isEmpty());
assertEquals("test", queryParameters.getQueryFragment());
}
@Before
public void setUp() throws Exception {
this.parser = new StrutsQueryStringParser(new StrutsUrlDecoder());
@@ -25,14 +25,10 @@ import org.springframework.mock.web.MockJspWriter;
import jakarta.servlet.jsp.JspException;
/**
*/
public class SetTagTest extends AbstractUITagTest {
Chewbacca chewie;
SetTag tag;
private Chewbacca chewie;
private SetTag tag;
public void testApplicationScope() throws JspException {
tag.setName("foo");
@@ -400,6 +396,50 @@ public class SetTagTest extends AbstractUITagTest {
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testShortVarNameInPageScope() throws JspException {
tag.setName("f");
tag.setValue("name");
tag.setScope("page");
tag.doStartTag();
tag.doEndTag();
assertEquals("chewie", pageContext.getAttribute("f"));
}
public void testShortVarNameInRequestScope() throws JspException {
tag.setName("f");
tag.setValue("name");
tag.setScope("request");
tag.doStartTag();
tag.doEndTag();
assertEquals("chewie", request.getAttribute("f"));
}
public void testShortVarNameInSessionScope() throws JspException {
tag.setName("f");
tag.setValue("name");
tag.setScope("session");
tag.doStartTag();
tag.doEndTag();
assertEquals("chewie", session.get("f"));
}
public void testShortVarNameInApplicationScope() throws JspException {
tag.setName("f");
tag.setValue("name");
tag.setScope("application");
tag.doStartTag();
tag.doEndTag();
assertEquals("chewie", servletContext.getAttribute("f"));
}
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -411,9 +451,9 @@ public class SetTagTest extends AbstractUITagTest {
}
public class Chewbacca {
String name;
boolean furry;
public static class Chewbacca {
private String name;
private boolean furry;
public Chewbacca(String name, boolean furry) {
this.name = name;
@@ -2068,6 +2068,42 @@ public class URLTagTest extends AbstractUITagTest {
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
public void testQueryParamsAndFragment() throws Exception {
request.setRequestURI("/public/about");
tag.setAction("company");
tag.setValue("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false");
tag.setEscapeAmp("false");
tag.doStartTag();
tag.doEndTag();
assertEquals("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false", writer.toString());
}
public void testDoubleEqualSigns() throws Exception {
request.setRequestURI("/public/about");
tag.setAction("company");
tag.setValue("/PublicationsDetail.aspx?ID=GjTu91suYQI=&t=1");
tag.setEscapeAmp("false");
tag.doStartTag();
tag.doEndTag();
assertEquals("/PublicationsDetail.aspx?ID=GjTu91suYQI%3D&t=1", writer.toString());
}
public void testOnlyFragment() throws Exception {
request.setRequestURI("/public/about");
tag.setAction("company");
tag.setValue("/books#v=twopage&q&f=false");
tag.setEscapeAmp("false");
tag.doStartTag();
tag.doEndTag();
assertEquals("/books#v=twopage&q&f=false", writer.toString());
}
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -217,23 +217,9 @@ public class DebugTagTest extends AbstractUITagTest {
/**
* Overwrite the Struts Constant and reload container
*/
private void setStrutsConstant(final Map<String, String> overwritePropeties) {
configurationManager.addContainerProvider(new StubConfigurationProvider() {
@Override
public boolean needsReload() {
return true;
}
@Override
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
}
}
});
configurationManager.reload();
container = configurationManager.getConfiguration().getContainer();
@Override
protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
super.setStrutsConstant(overwritePropeties);
stack.getActionContext().withContainer(container);
}
}
}
@@ -1,3 +1,5 @@
<?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
@@ -16,17 +18,20 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
"struts-6.5.dtd">
<struts>
<package name="default" namespace="/default">
<action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
import java.util.Map;
<package name="parentLevelTwo" namespace="/parent2" final="true">
<action name="levelTwo" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
@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);
}
}
<package name="child" namespace="/child" extends="default,parentLevelTwo">
<action name="single" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
</struts>
@@ -1,3 +1,5 @@
<?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
@@ -16,25 +18,20 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
"struts-6.5.dtd">
<struts>
<package name="default" namespace="/default">
<action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
import java.util.Map;
<package name="finalPackage" namespace="/final" extends="default" final="true">
<action name="actionFinal" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
/**
* Actions implementing the CookiesAware interface will receive
* a Map of filtered cookies via the setCookiesMap method.
*
* Please note that the {@link CookieInterceptor} needs to be
* activated to receive a cookies map.
*
* @deprecated please use {@link org.apache.struts2.action.CookiesAware} instead
*/
@Deprecated
public interface CookiesAware {
/**
* Sets a map of filtered cookies.
* @param cookies the cookies
* @deprecated please use {@link org.apache.struts2.action.CookiesAware#withCookies(Map)} instead
*/
void setCookiesMap(Map<String, String> cookies);
}
<package name="normalPackage" namespace="/normal" extends="default">
<action name="actionNormal" class="com.opensymphony.xwork2.ActionSupport"/>
</package>
</struts>
@@ -26,4 +26,12 @@
<message>You must enter a context.</message>
</field-validator>
</field>
<field name="birthday">
<field-validator type="fieldexpression">
<param name="expression"><![CDATA[
(birthday == null || birthday.before(new java.util.Date()))
]]></param>
<message key="errors_birthday" />
</field-validator>
</field>
</validators>
-4
View File
@@ -1,4 +0,0 @@
# Struts 2 Freemarker Jakarta
This uses the eclipse transformer maven plugin to make the Freemarker dependency Jakarta EE 9+ compatible.
-69
View File
@@ -1,69 +0,0 @@
<?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.
*/
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.0-M6</version>
</parent>
<artifactId>struts2-freemarker-jakarta</artifactId>
<packaging>jar</packaging>
<name>Struts 2 Jakarta EE Compatible Freemarker</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.transformer</groupId>
<artifactId>transformer-maven-plugin</artifactId>
<version>0.5.0</version>
<extensions>true</extensions>
<configuration>
<rules>
<jakartaDefaults>true</jakartaDefaults>
</rules>
</configuration>
<executions>
<execution>
<id>eclipse-transformer-freemarker-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<artifact>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>${freemarker.version}</version>
</artifact>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+1 -2
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-jakarta</artifactId>
<packaging>pom</packaging>
@@ -37,7 +37,6 @@
</properties>
<modules>
<module>freemarker-jakarta</module>
<module>sitemesh2-jakarta</module>
<module>velocity-tools-view-jakarta</module>
<module>velocity-tools-jsp-jakarta</module>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.0-M6</version>
<version>7.0.0-M8</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-M6</version>
<version>7.0.0-M8</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-M6</version>
<version>7.0.0-M8</version>
</parent>
<artifactId>struts2-velocity-tools-view-jakarta</artifactId>
<packaging>jar</packaging>

Some files were not shown because too many files have changed in this diff Show More