Compare commits

...

98 Commits

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 01:10:03 +00:00
Lukasz Lenart b34665046c Improves code quality
Uses existing function to split input using comma
2025-02-02 17:22:42 +01:00
Lukasz Lenart 080263e93f [maven-release-plugin] prepare for next development iteration 2025-02-02 08:26:01 +01:00
Lukasz Lenart f6bf43ae0b [maven-release-plugin] prepare release STRUTS_7_0_1 2025-02-02 08:25:53 +01:00
Lukasz Lenart 25d0338e63 Merge pull request #1199 from apache/dependabot/maven/org.apache.commons-commons-text-1.13.0
Bump org.apache.commons:commons-text from 1.12.0 to 1.13.0
2025-01-27 07:00:15 +01:00
Lukasz Lenart 01af64615f Merge pull request #1198 from apache/dependabot/maven/byte-buddy.version-1.16.1
Bump byte-buddy.version from 1.15.11 to 1.16.1
2025-01-27 06:58:56 +01:00
Lukasz Lenart ebc4e3516d Merge pull request #1196 from apache/dependabot/github_actions/github/codeql-action-3.28.5
Bump github/codeql-action from 3.28.1 to 3.28.5
2025-01-27 06:56:56 +01:00
dependabot[bot] d5b94369d1 Bump org.apache.commons:commons-text from 1.12.0 to 1.13.0
Bumps org.apache.commons:commons-text from 1.12.0 to 1.13.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 01:54:25 +00:00
dependabot[bot] c5cf2f8f99 Bump byte-buddy.version from 1.15.11 to 1.16.1
Bumps `byte-buddy.version` from 1.15.11 to 1.16.1.

Updates `net.bytebuddy:byte-buddy` from 1.15.11 to 1.16.1
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.15.11...byte-buddy-1.16.1)

Updates `net.bytebuddy:byte-buddy-agent` from 1.15.11 to 1.16.1
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.15.11...byte-buddy-1.16.1)

---
updated-dependencies:
- dependency-name: net.bytebuddy:byte-buddy
  dependency-type: direct:development
  update-type: version-update:semver-minor
- dependency-name: net.bytebuddy:byte-buddy-agent
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 01:54:18 +00:00
dependabot[bot] 10d3545ac2 Bump github/codeql-action from 3.28.1 to 3.28.5
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.1 to 3.28.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.1...v3.28.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 01:45:24 +00:00
Lukasz Lenart 05deb7b5e7 Merge pull request #1191 from apache/feature/WW-5512-optional-inject-s7
WW-5512 Extends the container to support injecting optional parameters into constructor
2025-01-26 08:19:06 +01:00
Lukasz Lenart 358f808567 WW-5512 Extends the container to support injecting optional parameters into constructor 2025-01-23 08:58:46 +01:00
Kusal Kithul-Godage f98b67562b Merge pull request #1193 from apache/WW-5516-fix-temp-theme-fallback-70 2025-01-22 17:42:17 +11:00
Kusal Kithul-Godage 2def24c7db WW-5516 Fix TemplateDir and Theme fallback to Request/Session/Application attributes 2025-01-22 13:25:01 +11:00
Lukasz Lenart 4e80f2661e Merge pull request #1187 from apache/fix/WW-5517-debug
WW-5517 Fixes <s:debug/> to be compatible with allowlist capability
2025-01-21 18:01:26 +01:00
Lukasz Lenart 9ba5c095fb Merge pull request #1189 from apache/dependabot/maven/org.sitemesh-sitemesh-3.2.2
Bump org.sitemesh:sitemesh from 3.2.1 to 3.2.2
2025-01-21 18:01:05 +01:00
Lukasz Lenart 8f4fa06855 WW-5517 Adds a dedicate test case to cover allowlist 2025-01-20 09:10:02 +01:00
dependabot[bot] 181f0073ff Bump org.sitemesh:sitemesh from 3.2.1 to 3.2.2
Bumps [org.sitemesh:sitemesh](https://github.com/sitemesh/sitemesh3) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/sitemesh/sitemesh3/releases)
- [Commits](https://github.com/sitemesh/sitemesh3/compare/3.2.1...3.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 01:07:47 +00:00
Lukasz Lenart f3997f705e WW-5517 Fixes <s:debug/> to be compatible with allowlist capability 2025-01-18 21:57:55 +01:00
Lukasz Lenart b054b51929 Merge pull request #1186 from apache/fix/nightlies-67
Adds 6.7.x branch to nigthlies step
2025-01-18 10:03:31 +01:00
Lukasz Lenart e0be8270ca Merge pull request #1185 from apache/feature/WW-5510-tooltip
WW-5510 Marks support for tooltips as deprecated
2025-01-18 09:46:36 +01:00
Lukasz Lenart 32cd5d0dcc Adds 6.7.x branch to nigthlies step 2025-01-18 09:45:51 +01:00
Lukasz Lenart c2a5bfe3c9 WW-5510 Marks support for tooltips as deprecated
This will be removed in the minor release, migrate to use native html tooltip support
2025-01-16 10:31:43 +01:00
Lukasz Lenart fa60e1ceb2 Merge pull request #1184 from apache/fix/WW-5501-exclude-s7
WW-5501 Only exclude malicious file names
2025-01-16 10:19:15 +01:00
Lukasz Lenart 4279511c5b WW-5501 Only exclude malicious file names 2025-01-16 08:34:57 +01:00
Lukasz Lenart 71bca19c13 Merge pull request #1176 from apache/dependabot/maven/byte-buddy.version-1.15.11
Bump byte-buddy.version from 1.14.11 to 1.15.11
2025-01-16 07:58:38 +01:00
Lukasz Lenart 30aefaa5d3 Merge pull request #1183 from apache/dependabot/github_actions/actions/upload-artifact-4.6.0
Bump actions/upload-artifact from 4.5.0 to 4.6.0
2025-01-16 07:57:28 +01:00
Lukasz Lenart 2c023a245d Merge pull request #1182 from apache/dependabot/github_actions/github/codeql-action-3.28.1
Bump github/codeql-action from 3.28.0 to 3.28.1
2025-01-16 07:57:10 +01:00
Lukasz Lenart cebdf6df82 Merge pull request #1178 from apache/dependabot/maven/log4j2.version-2.24.3
Bump log4j2.version from 2.24.2 to 2.24.3
2025-01-16 07:56:53 +01:00
dependabot[bot] 6d01916f12 Bump actions/upload-artifact from 4.5.0 to 4.6.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/6f51ac03b9356f520e9adb1b1b7802705f340c2b...65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 01:55:37 +00:00
dependabot[bot] 6cc4a105e6 Bump github/codeql-action from 3.28.0 to 3.28.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.0 to 3.28.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.0...v3.28.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 01:55:35 +00:00
Kusal Kithul-Godage b5d94a1f07 Merge pull request #1179 from apache/excl-list
Add jakarta package to exclusion list
2025-01-06 21:23:41 +11:00
Kusal Kithul-Godage 9e93bb51db Add jakarta package to exclusion list 2025-01-06 13:46:21 +11:00
dependabot[bot] 0931b88b11 Bump log4j2.version from 2.24.2 to 2.24.3
Bumps `log4j2.version` from 2.24.2 to 2.24.3.

Updates `org.apache.logging.log4j:log4j-api` from 2.24.2 to 2.24.3

Updates `org.apache.logging.log4j:log4j-core` from 2.24.2 to 2.24.3

Updates `org.apache.logging.log4j:log4j-jcl` from 2.24.2 to 2.24.3

Updates `org.apache.logging.log4j:log4j-slf4j-impl` from 2.24.2 to 2.24.3

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 01:11:00 +00:00
dependabot[bot] 8dfc157be0 Bump byte-buddy.version from 1.14.11 to 1.15.11
Bumps `byte-buddy.version` from 1.14.11 to 1.15.11.

Updates `net.bytebuddy:byte-buddy` from 1.14.11 to 1.15.11
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.11...byte-buddy-1.15.11)

Updates `net.bytebuddy:byte-buddy-agent` from 1.14.11 to 1.15.11
- [Release notes](https://github.com/raphw/byte-buddy/releases)
- [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md)
- [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.11...byte-buddy-1.15.11)

---
updated-dependencies:
- dependency-name: net.bytebuddy:byte-buddy
  dependency-type: direct:development
  update-type: version-update:semver-minor
- dependency-name: net.bytebuddy:byte-buddy-agent
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-06 01:10:50 +00:00
Lukasz Lenart 98df24a2a0 Merge pull request #1171 from apache/fix/WW-5500-s7
WW-5500 Extends pattern to validate multipart uploads
2025-01-05 17:47:51 +01:00
Lukasz Lenart fd24a4a9c8 Merge pull request #1170 from apache/fix/WW-5498-s7
WW-5498 Adds devMode errors as action messages to avoid breaking validation logic
2025-01-05 17:47:14 +01:00
Lukasz Lenart 026300b456 Merge pull request #1133 from apache/dependabot/maven/commons-io-commons-io-2.18.0
Bump commons-io:commons-io from 2.15.1 to 2.18.0
2025-01-05 10:18:51 +01:00
Lukasz Lenart b36f7bd9cc Merge pull request #1173 from apache/fix/dependabot
Updates dependabot.yml
2025-01-05 10:07:14 +01:00
dependabot[bot] 93943dbb33 Bump commons-io:commons-io from 2.15.1 to 2.18.0
Bumps commons-io:commons-io from 2.15.1 to 2.18.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-05 09:01:48 +00:00
Lukasz Lenart 18f3a49028 Updatex dependabot.yml
Stop ignoring any dependency
2025-01-05 09:54:29 +01:00
Lukasz Lenart 9e2f92f7af Merge pull request #1172 from apache/fix/supported-versions
Updates SECURITY.md
2025-01-05 09:46:22 +01:00
Lukasz Lenart f97e2c29bc Updates SECURITY.md 2025-01-05 09:34:58 +01:00
Lukasz Lenart bd46f587a4 Merge pull request #1164 from apache/dependabot/maven/org.apache.velocity-velocity-engine-core-2.4.1
Bump org.apache.velocity:velocity-engine-core from 2.3 to 2.4.1
2025-01-05 09:29:57 +01:00
Lukasz Lenart e5825de28e Merge pull request #1163 from apache/dependabot/maven/org.freemarker-freemarker-2.3.34
Bump org.freemarker:freemarker from 2.3.33 to 2.3.34
2025-01-05 09:26:32 +01:00
Lukasz Lenart 6d71674cc1 WW-5500 Extends pattern to validate multipart uploads 2025-01-05 09:21:08 +01:00
Lukasz Lenart db9684e1c1 WW-5498 Adds devMode errors as action messages to avoid breaking validation logic 2025-01-05 09:18:40 +01:00
Lukasz Lenart ac9a02c685 Merge pull request #1166 from apache/fix/sonar-scan
Removes checking branch ref and removes branch name as not needed
2025-01-04 14:50:48 +01:00
Lukasz Lenart 9ac86ed61b Merge pull request #1165 from apache/feature/yourkit
Add YourKit thank you section
2025-01-04 14:38:11 +01:00
Lukasz Lenart c0a720475b Removes checking branch ref and removes branch name as not needed 2025-01-03 16:29:55 +01:00
Lukasz Lenart cadb7a1fb2 Add YourKit thank you section 2025-01-03 08:53:00 +01:00
dependabot[bot] 4f1556c2d7 Bump org.apache.velocity:velocity-engine-core from 2.3 to 2.4.1
Bumps org.apache.velocity:velocity-engine-core from 2.3 to 2.4.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 01:52:04 +00:00
dependabot[bot] 80b3108c86 Bump org.freemarker:freemarker from 2.3.33 to 2.3.34
Bumps org.freemarker:freemarker from 2.3.33 to 2.3.34.

---
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-12-30 01:52:00 +00:00
Lukasz Lenart 375d00aa8e Merge pull request #1162 from apache/fix/strict-branch
Uses exact branch name instead of wildcard
2024-12-24 10:23:11 +01:00
Lukasz Lenart 44ff04683a Uses exact branch name instead of wildcard 2024-12-24 08:32:08 +01:00
Lukasz Lenart 2850315cf3 Merge pull request #1159 from apache/feature/WW-5503-removes-deps
WW-5503 Removes unused dependencies
2024-12-24 07:52:41 +01:00
Lukasz Lenart e695ac8a4e Merge pull request #1161 from apache/feature/wildcard-packages
Adds additional test cases to match Struts packages
2024-12-24 07:15:44 +01:00
Lukasz Lenart 0114dff50d WW-5503 Removes unused dependencies 2024-12-24 07:14:49 +01:00
Lukasz Lenart 7c62bfc5a4 Merge pull request #1160 from ljharb/patch-1
[readme] add link to struts site commercial support page
2024-12-23 20:43:15 +01:00
Lukasz Lenart 3be56a4840 Adds additional test cases to match Struts packages 2024-12-23 19:16:57 +01:00
Jordan Harband a9662ee583 [readme] add link to struts site commercial support page
Link directly to https://struts.apache.org/commercial-support.html on the readme in the relevant section.
2024-12-23 10:04:16 -08:00
Lukasz Lenart 43295b1f42 Merge pull request #1152 from apache/dependabot/maven/org.codehaus.mojo-versions-maven-plugin-2.18.0
Bump org.codehaus.mojo:versions-maven-plugin from 2.17.1 to 2.18.0
2024-12-23 16:12:33 +01:00
Lukasz Lenart faa3e723aa Merge pull request #1154 from apache/dependabot/github_actions/github/codeql-action-3.28.0
Bump github/codeql-action from 3.27.9 to 3.28.0
2024-12-23 16:12:22 +01:00
Lukasz Lenart 92b2cb9ee1 Merge pull request #1155 from apache/dependabot/github_actions/actions/upload-artifact-4.5.0
Bump actions/upload-artifact from 4.4.3 to 4.5.0
2024-12-23 16:12:11 +01:00
Lukasz Lenart 36ca20d3dc Merge pull request #1157 from apache/feature/WW-5501-exclude-s7
WW-5501 Excludes malicious names
2024-12-23 15:09:05 +01:00
Lukasz Lenart bbdb38dedc WW-5501 Uses StringUtils.normalizeSpace instead of sanitizeNewlines 2024-12-23 14:26:30 +01:00
Lukasz Lenart 552f7eba77 WW-5501 Excludes malicious names 2024-12-23 12:39:57 +01:00
dependabot[bot] 196aa3f366 Bump actions/upload-artifact from 4.4.3 to 4.5.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.3 to 4.5.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882...6f51ac03b9356f520e9adb1b1b7802705f340c2b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 02:01:04 +00:00
dependabot[bot] 0792d0586f Bump github/codeql-action from 3.27.9 to 3.28.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.9 to 3.28.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.27.9...v3.28.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 02:01:01 +00:00
dependabot[bot] 6b07ee83ee Bump org.codehaus.mojo:versions-maven-plugin from 2.17.1 to 2.18.0
Bumps [org.codehaus.mojo:versions-maven-plugin](https://github.com/mojohaus/versions) from 2.17.1 to 2.18.0.
- [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.17.1...2.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 01:10:38 +00:00
Lukasz Lenart 0bcb6516bf Merge pull request #1151 from apache/feature/homepage
Adds info about homepage and descritpion to Github repo
2024-12-20 08:06:38 +01:00
Lukasz Lenart dba7510984 Adds info about homepage and descritpion to Github repo 2024-12-20 07:47:53 +01:00
Lukasz Lenart d7188c18d2 Merge pull request #1149 from apache/dependabot/github_actions/github/codeql-action-3.27.9
Bump github/codeql-action from 3.27.7 to 3.27.9
2024-12-18 15:42:00 +01:00
Lukasz Lenart 1655838220 Merge pull request #1148 from apache/dependabot/maven/org.codehaus.mojo-exec-maven-plugin-3.5.0
Bump org.codehaus.mojo:exec-maven-plugin from 3.4.1 to 3.5.0
2024-12-18 15:41:39 +01:00
Lukasz Lenart e9a12dd94f Merge pull request #1147 from apache/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.5.2
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.1 to 3.5.2
2024-12-18 15:41:18 +01:00
dependabot[bot] ddf80bec29 Bump github/codeql-action from 3.27.7 to 3.27.9
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.7 to 3.27.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v3.27.7...v3.27.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 01:40:42 +00:00
dependabot[bot] e81caf5cec Bump org.codehaus.mojo:exec-maven-plugin from 3.4.1 to 3.5.0
Bumps [org.codehaus.mojo:exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases)
- [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/3.4.1...3.5.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-12-16 01:16:41 +00:00
dependabot[bot] 9d2eb5e607 Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.5.1 to 3.5.2
Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.5.1 to 3.5.2.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.1...surefire-3.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 01:16:38 +00:00
Lukasz Lenart 9aa41f18ae [maven-release-plugin] prepare for next development iteration 2024-12-11 07:56:30 +01:00
72 changed files with 1592 additions and 610 deletions
+3 -1
View File
@@ -11,6 +11,8 @@ notifications:
jira_options: link label worklog
github:
description: "Apache Struts is a free, open-source, MVC framework for creating elegant, modern Java web applications"
homepage: https://struts.apache.org/
del_branch_on_merge: true
protected_branches:
main:
@@ -21,7 +23,7 @@ github:
# it does not work because our github teams are private/secret, see INFRA-25666
require_code_owner_reviews: false
required_approving_review_count: 0
release/*:
release/struts-6-7-x:
# contexts are the names of checks that must pass.
contexts:
- build
+1
View File
@@ -8,3 +8,4 @@ updates:
directory: "/"
schedule:
interval: "weekly"
ignore: []
+3 -3
View File
@@ -52,12 +52,12 @@ jobs:
java-version: 17
cache: 'maven'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.27.7
uses: github/codeql-action/init@v3.28.9
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3.27.7
uses: github/codeql-action/autobuild@v3.28.9
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.27.7
uses: github/codeql-action/analyze@v3.28.9
with:
category: "/language:${{matrix.language}}"
+2 -2
View File
@@ -58,13 +58,13 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # 4.4.3
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # 4.6.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@89757925c7adddb19b7a2f28e3e1b27da88b7304 # 2.22.11
uses: github/codeql-action/upload-sarif@0a35e8f6866a39b001e5f7ad1d0daf9836786896 # 2.22.11
with:
sarif_file: results.sarif
+1 -3
View File
@@ -44,6 +44,4 @@ jobs:
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
MAIN_BRANCH: ${{ github.ref || github.base_ref || github.ref || github.base_ref }}
if: ${{ github.ref == 'main' || github.base_ref == 'main' }}
run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.branch.name=${{ env.MAIN_BRANCH }} -Pcoverage -DskipAssembly
run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Pcoverage -DskipAssembly
Vendored
+3
View File
@@ -87,6 +87,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'release/struts-6-7-x'
}
}
steps {
@@ -100,6 +101,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'release/struts-6-7-x'
}
}
steps {
@@ -112,6 +114,7 @@ pipeline {
when {
anyOf {
branch 'main'
branch 'release/struts-6-7-x'
}
}
steps {
+8 -1
View File
@@ -103,8 +103,15 @@ the Struts approach.
## Commercial Support
The Apache Struts community does not offer commercial support by itself.
The Apache Struts community does not offer commercial support by itself, but we maintain [a list of companies offering
commercial support on our website](https://struts.apache.org/commercial-support.html).
Some Apache Struts maintainers are working with [Tidelift](https://tidelift.com/) to provide commercial support and
invest paid working time in the improvement of the Apache Struts framework. For more information, visit
the [Tidelift resources regarding Apache Struts](https://tidelift.com/subscription/pkg/maven-org-apache-struts-struts2-core?utm_source=maven-org-apache-struts-struts2-core&utm_medium=referral&utm_campaign=readme)
## Thank you
[YourKit](https://www.yourkit.com/) is kindly supporting open source projects with its full-featured Java Profiler.
YourKit is the creator of innovative and intelligent tools for profiling Java and .NET applications.
As an Apache committer, you can get a free license at [YourKit's open source sponsorship program](https://www.yourkit.com/java/profiler/purchase/#os_license).
+3 -2
View File
@@ -7,8 +7,9 @@ and what potential vulnerability it can have:
| Version | Supported |
|---------|--------------------|
| 6.x | :white_check_mark: |
| 2.5.x | :white_check_mark: |
| 7.x | :white_check_mark: |
| 6.7.x | :white_check_mark: |
| 2.5.x | ❌ |
## Reporting New Security Issues with thr Apache Struts
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-apps</artifactId>
<packaging>pom</packaging>
+2 -2
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>7.0.0</version>
<version>7.0.3</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
+3 -3
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -123,7 +123,7 @@
<dependency>
<groupId>org.sitemesh</groupId>
<artifactId>sitemesh</artifactId>
<version>3.2.1</version>
<version>3.2.2</version>
</dependency>
<dependency>
@@ -207,7 +207,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.1</version>
<version>3.5.2</version>
<configuration>
<includes>
<include>it.org.apache.struts2.showcase.*Test</include>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-assembly</artifactId>
+4 -4
View File
@@ -25,11 +25,11 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-bom</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -44,7 +44,7 @@
</licenses>
<properties>
<struts-version.version>7.0.0</struts-version.version>
<struts-version.version>7.0.3</struts-version.version>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
@@ -160,7 +160,7 @@
</dependencyManagement>
<scm>
<tag>STRUTS_7_0_0</tag>
<tag>STRUTS_7_0_3</tag>
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -18,7 +18,10 @@
*/
package org.apache.struts2.components;
import org.apache.commons.lang3.ClassUtils;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.CompoundRoot;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import jakarta.servlet.http.HttpServletRequest;
@@ -40,6 +43,7 @@ public class Debug extends UIBean {
protected ReflectionProvider reflectionProvider;
private ThreadAllowlist threadAllowlist;
public Debug(ValueStack stack, HttpServletRequest request, HttpServletResponse response) {
super(stack, request, response);
@@ -50,6 +54,11 @@ public class Debug extends UIBean {
this.reflectionProvider = prov;
}
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
protected String getDefaultTemplate() {
return TEMPLATE;
}
@@ -59,16 +68,19 @@ public class Debug extends UIBean {
if (showDebug()) {
ValueStack stack = getStack();
Iterator iter = stack.getRoot().iterator();
List stackValues = new ArrayList(stack.getRoot().size());
allowList(stack.getRoot());
Iterator<Object> iter = stack.getRoot().iterator();
List<Object> stackValues = new ArrayList<>(stack.getRoot().size());
while (iter.hasNext()) {
Object o = iter.next();
Map values;
Map<String, Object> values;
try {
values = reflectionProvider.getBeanMap(o);
} catch (Exception e) {
throw new StrutsException("Caught an exception while getting the property values of " + o, e);
}
allowListClass(o);
stackValues.add(new DebugMapEntry(o.getClass().getName(), values));
}
@@ -77,6 +89,16 @@ public class Debug extends UIBean {
return result;
}
private void allowList(CompoundRoot root) {
root.forEach(this::allowListClass);
}
private void allowListClass(Object o) {
threadAllowlist.allowClass(o.getClass());
ClassUtils.getAllSuperclasses(o.getClass()).forEach(threadAllowlist::allowClass);
ClassUtils.getAllInterfaces(o.getClass()).forEach(threadAllowlist::allowClass);
}
@Override
public boolean end(Writer writer, String body) {
if (showDebug()) {
@@ -91,17 +113,17 @@ public class Debug extends UIBean {
return (devMode || Boolean.TRUE == PrepareOperations.getDevModeOverride());
}
private static class DebugMapEntry implements Map.Entry {
private final Object key;
private static class DebugMapEntry implements Map.Entry<String, Object> {
private final String key;
private Object value;
DebugMapEntry(Object key, Object value) {
DebugMapEntry(String key, Object value) {
this.key = key;
this.value = value;
}
@Override
public Object getKey() {
public String getKey() {
return key;
}
@@ -18,13 +18,12 @@
*/
package org.apache.struts2.components;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* <!-- START SNIPPET: javadoc -->
* <p>Render an HTML input field of type text</p>
@@ -50,17 +49,16 @@ import jakarta.servlet.http.HttpServletResponse;
* </pre>
*/
@StrutsTag(
name="textfield",
tldTagClass="org.apache.struts2.views.jsp.ui.TextFieldTag",
description="Render an HTML input field of type text",
allowDynamicAttributes=true)
name = "textfield",
tldTagClass = "org.apache.struts2.views.jsp.ui.TextFieldTag",
description = "Render an HTML input field of type text",
allowDynamicAttributes = true)
public class TextField extends UIBean {
/**
* The name of the default template for the TextFieldTag
*/
final public static String TEMPLATE = "text";
protected String maxlength;
protected String readonly;
protected String size;
@@ -95,27 +93,22 @@ public class TextField extends UIBean {
}
@StrutsTagAttribute(description="HTML maxlength attribute", type="Integer")
@StrutsTagAttribute(description = "HTML maxlength attribute", type = "Integer")
public void setMaxlength(String maxlength) {
this.maxlength = maxlength;
}
@StrutsTagAttribute(description="Deprecated. Use maxlength instead.", type="Integer")
public void setMaxLength(String maxlength) {
this.maxlength = maxlength;
}
@StrutsTagAttribute(description="Whether the input is readonly", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether the input is readonly", type = "Boolean", defaultValue = "false")
public void setReadonly(String readonly) {
this.readonly = readonly;
}
@StrutsTagAttribute(description="HTML size attribute", type="Integer")
@StrutsTagAttribute(description = "HTML size attribute", type = "Integer")
public void setSize(String size) {
this.size = size;
}
@StrutsTagAttribute(description="Specifies the html5 type element to display. e.g. text, email, url", defaultValue="text")
@StrutsTagAttribute(description = "Specifies the html5 type element to display. e.g. text, email, url", defaultValue = "text")
public void setType(String type) {
this.type = type;
}
@@ -26,7 +26,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
@@ -35,6 +34,7 @@ import org.apache.struts2.components.template.Template;
import org.apache.struts2.components.template.TemplateEngine;
import org.apache.struts2.components.template.TemplateEngineManager;
import org.apache.struts2.components.template.TemplateRenderingContext;
import org.apache.struts2.dispatcher.AttributeMap;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.apache.struts2.util.ComponentUtils;
import org.apache.struts2.util.TextProviderHelper;
@@ -48,6 +48,10 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static java.util.Collections.emptyMap;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.struts2.dispatcher.DispatcherConstants.ATTRIBUTES;
/**
* <p>
* UIBean is the standard superclass of all Struts UI components.
@@ -300,7 +304,7 @@ import java.util.function.Function;
* <!-- END SNIPPET: javascriptRelatedAttributes -->
*
* <!-- START SNIPPET: tooltipattributes -->
*
* <strong>Deprecated since 7.0.1</strong
* <table border="1" summary="">
* <tr>
* <td>Attribute</td>
@@ -344,7 +348,7 @@ import java.util.function.Function;
*
* <!-- START SNIPPET: tooltipdescription -->
* <p>
* <b>tooltipConfig is deprecated, use individual tooltip configuration attributes instead </b>
* <strong>tooltipConfig is deprecated, use individual tooltip configuration attributes instead </strong>
* </p>
*
* <p>
@@ -380,7 +384,7 @@ import java.util.function.Function;
*
* <pre>
* <!-- START SNIPPET: tooltipexample -->
*
* <strong>Deprecated since 7.0.1</strong>
* &lt;!-- Example 1: --&gt;
* &lt;s:form
* tooltipDelay="500"
@@ -440,6 +444,9 @@ public abstract class UIBean extends Component {
private static final Logger LOG = LogManager.getLogger(UIBean.class);
static final String TEMPLATE_DIR = "templateDir";
static final String THEME = "theme";
protected static final String ATTR_FIELD_VALUE = "fieldValue";
protected static final String ATTR_NAME_VALUE = "nameValue";
protected static final String ATTR_VALUE = "value";
@@ -602,13 +609,18 @@ public abstract class UIBean extends Component {
result = findString(this.templateDir);
}
// Check Request, Session, Application scopes
if (isBlank(result)) {
result = (String) getAttrMap().get(TEMPLATE_DIR);
}
// Default template set
if (StringUtils.isBlank(result)) {
if (isBlank(result)) {
result = defaultTemplateDir;
}
// Defaults to 'template'
if (StringUtils.isBlank(result)) {
if (isBlank(result)) {
result = "template";
}
@@ -622,26 +634,36 @@ public abstract class UIBean extends Component {
result = findString(this.theme);
}
if (StringUtils.isBlank(result)) {
if (isBlank(result)) {
Form form = (Form) findAncestor(Form.class);
if (form != null) {
result = form.getTheme();
}
}
// Check Request, Session, Application scopes
if (isBlank(result)) {
result = (String) getAttrMap().get(THEME);
}
// Default theme set
if (StringUtils.isBlank(result)) {
if (isBlank(result)) {
result = defaultUITheme;
}
return result;
}
private Map<String, Object> getAttrMap() {
AttributeMap attrMap = (AttributeMap) getStack().getContext().get(ATTRIBUTES);
return attrMap != null ? attrMap : emptyMap();
}
public void evaluateParams() {
String gotTheme = getTheme();
addParameter("templateDir", getTemplateDir());
addParameter("theme", gotTheme);
addParameter(TEMPLATE_DIR, getTemplateDir());
addParameter(THEME, gotTheme);
addParameter("template", template != null ? findString(template) : getDefaultTemplate());
addParameter("dynamicAttributes", dynamicAttributes);
addParameter("themeExpansionToken", uiThemeExpansionToken);
@@ -968,6 +990,7 @@ public abstract class UIBean extends Component {
}
}
@Deprecated(since = "7.0.1", forRemoval = true)
protected Map<String, String> getTooltipConfig(UIBean component) {
Object tooltipConfigObj = component.getAttributes().get("tooltipConfig");
Map<String, String> result = new LinkedHashMap<>();
@@ -1237,11 +1260,13 @@ public abstract class UIBean extends Component {
}
@StrutsTagAttribute(description="Set the tooltip of this particular component")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltip(String tooltip) {
this.tooltip = tooltip;
}
@StrutsTagAttribute(description="Deprecated. Use individual tooltip configuration attributes instead.")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipConfig(String tooltipConfig) {
this.tooltipConfig = tooltipConfig;
}
@@ -1252,22 +1277,26 @@ public abstract class UIBean extends Component {
}
@StrutsTagAttribute(description="Use JavaScript to generate tooltips", type="Boolean", defaultValue="false")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setJavascriptTooltip(String javascriptTooltip) {
this.javascriptTooltip = javascriptTooltip;
}
@StrutsTagAttribute(description="CSS class applied to JavaScrip tooltips", defaultValue="StrutsTTClassic")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipCssClass(String tooltipCssClass) {
this.tooltipCssClass = tooltipCssClass;
}
@StrutsTagAttribute(description="Delay in milliseconds, before showing JavaScript tooltips ",
defaultValue="Classic")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipDelay(String tooltipDelay) {
this.tooltipDelay = tooltipDelay;
}
@StrutsTagAttribute(description="Icon path used for image that will have the tooltip")
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipIconPath(String tooltipIconPath) {
this.tooltipIconPath = tooltipIconPath;
}
@@ -18,9 +18,9 @@
*/
package org.apache.struts2.dispatcher;
import jakarta.servlet.jsp.PageContext;
import org.apache.struts2.StrutsStatics;
import jakarta.servlet.jsp.PageContext;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
@@ -86,7 +86,7 @@ public class AttributeMap extends AbstractMap<String, Object> {
PageContext pc = getPageContext();
if (pc == null) {
if (pc == null || pc.getRequest() == null) {
RequestMap request = (RequestMap) context.get(DispatcherConstants.REQUEST);
SessionMap session = (SessionMap) context.get(DispatcherConstants.SESSION);
ApplicationMap application = (ApplicationMap) context.get(DispatcherConstants.APPLICATION);
@@ -111,7 +111,7 @@ public class Dispatcher {
*/
public static final String REQUEST_POST_METHOD = "POST";
public static final String MULTIPART_FORM_DATA_REGEX = "^multipart/form-data(?:\\s*;\\s*boundary=[0-9a-zA-Z'()+_,\\-./:=?]{1,70})?(?:\\s*;\\s*charset=[a-zA-Z\\-0-9]{3,14})?";
public static final String MULTIPART_FORM_DATA_REGEX = "^multipart/form-data(?:\\s*;\\s*boundary=[0-9a-zA-Z'\"()+_,\\-./:=?]{1,70})?(?:\\s*;\\s*charset=[a-zA-Z\\-0-9]{3,14})?";
private static final String CONFIG_SPLIT_REGEX = "\\s*,\\s*";
@@ -26,6 +26,7 @@ import org.apache.commons.fileupload2.core.FileUploadException;
import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
import org.apache.commons.fileupload2.core.FileUploadSizeException;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -284,18 +285,13 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
* @return the canonical name based on the supplied filename
*/
protected String getCanonicalName(final String originalFileName) {
String fileName = originalFileName;
int forwardSlash = fileName.lastIndexOf('/');
int backwardSlash = fileName.lastIndexOf('\\');
if (forwardSlash != -1 && forwardSlash > backwardSlash) {
fileName = fileName.substring(forwardSlash + 1);
} else {
fileName = fileName.substring(backwardSlash + 1);
}
return fileName;
return FilenameUtils.getName(originalFileName);
}
/**
* @deprecated since 7.0.1, use {@link StringUtils#normalizeSpace(String)} instead
*/
@Deprecated
protected String sanitizeNewlines(String before) {
return before.replaceAll("\\R", "_");
}
@@ -32,6 +32,8 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
/**
* Multipart form data request adapter for Jakarta Commons FileUpload package.
*/
@@ -47,11 +49,11 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
prepareServletFileUpload(charset, Path.of(saveDir));
for (DiskFileItem item : servletFileUpload.parseRequest(request)) {
LOG.debug(() -> "Processing a form field: " + sanitizeNewlines(item.getFieldName()));
LOG.debug(() -> "Processing a form field: " + normalizeSpace(item.getFieldName()));
if (item.isFormField()) {
processNormalFormField(item, charset);
} else {
LOG.debug(() -> "Processing a file: " + sanitizeNewlines(item.getFieldName()));
LOG.debug(() -> "Processing a file: " + normalizeSpace(item.getFieldName()));
processFileField(item);
}
}
@@ -75,7 +77,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
protected void processNormalFormField(DiskFileItem item, Charset charset) throws IOException {
LOG.debug("Item: {} is a normal form field", item.getName());
LOG.debug("Item: {} is a normal form field", normalizeSpace(item.getName()));
List<String> values;
String fieldName = item.getFieldName();
@@ -100,7 +102,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
protected void processFileField(DiskFileItem item) {
// Skip file uploads that don't have a file name - meaning that no file was selected.
if (item.getName() == null || item.getName().trim().isEmpty()) {
LOG.debug(() -> "No file has been uploaded for the field: " + sanitizeNewlines(item.getFieldName()));
LOG.debug(() -> "No file has been uploaded for the field: " + normalizeSpace(item.getFieldName()));
return;
}
@@ -112,7 +114,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
if (item.isInMemory()) {
LOG.warn("Storing uploaded files just in memory isn't supported currently, skipping file: {}!", item.getName());
LOG.warn(() -> "Storing uploaded files just in memory isn't supported currently, skipping file: %s!".formatted(normalizeSpace(item.getName())));
} else {
UploadedFile uploadedFile = StrutsUploadedFile.Builder
.create(item.getPath().toFile())
@@ -42,6 +42,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
/**
* Multi-part form data request adapter for Jakarta Commons FileUpload package that
* leverages the streaming API rather than the traditional non-streaming API.
@@ -71,10 +73,10 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
LOG.debug("Using Jakarta Stream API to process request");
servletFileUpload.getItemIterator(request).forEachRemaining(item -> {
if (item.isFormField()) {
LOG.debug(() -> "Processing a form field: " + sanitizeNewlines(item.getFieldName()));
LOG.debug(() -> "Processing a form field: " + normalizeSpace(item.getFieldName()));
processFileItemAsFormField(item);
} else {
LOG.debug(() -> "Processing a file: " + sanitizeNewlines(item.getFieldName()));
LOG.debug(() -> "Processing a file: " + normalizeSpace(item.getFieldName()));
processFileItemAsFileField(item, location);
}
});
@@ -141,7 +143,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
if (maxFiles != null && maxFiles == uploadedFiles.size()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Cannot accept another file: {} as it will exceed max files: {}",
sanitizeNewlines(fileItemInput.getName()), maxFiles);
normalizeSpace(fileItemInput.getName()), maxFiles);
}
LocalizedMessage errorMessage = buildErrorMessage(
FileUploadFileCountLimitException.class,
@@ -160,7 +162,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
private void exceedsMaxSizeOfFiles(FileItemInput fileItemInput, File file, Long currentFilesSize) {
if (LOG.isDebugEnabled()) {
LOG.debug("File: {} of size: {} exceeds allowed max size: {}, actual size of already uploaded files: {}",
sanitizeNewlines(fileItemInput.getName()), file.length(), maxSizeOfFiles, currentFilesSize
normalizeSpace(fileItemInput.getName()), file.length(), maxSizeOfFiles, currentFilesSize
);
}
LocalizedMessage errorMessage = buildErrorMessage(
@@ -174,7 +176,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
}
if (!file.delete() && LOG.isWarnEnabled()) {
LOG.warn("Cannot delete file: {} which exceeds maximum size: {} of all files!",
sanitizeNewlines(fileItemInput.getName()), maxSizeOfFiles);
normalizeSpace(fileItemInput.getName()), maxSizeOfFiles);
}
}
@@ -187,7 +189,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
protected void processFileItemAsFileField(FileItemInput fileItemInput, Path location) throws IOException {
// Skip file uploads that don't have a file name - meaning that no file was selected.
if (fileItemInput.getName() == null || fileItemInput.getName().trim().isEmpty()) {
LOG.debug(() -> "No file has been uploaded for the field: " + sanitizeNewlines(fileItemInput.getFieldName()));
LOG.debug(() -> "No file has been uploaded for the field: " + normalizeSpace(fileItemInput.getFieldName()));
return;
}
@@ -230,7 +232,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
InputStream input = fileItemInput.getInputStream();
try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
byte[] buffer = new byte[bufferSize];
LOG.debug("Streaming file: {} using buffer size: {}", fileItemInput.getName(), bufferSize);
LOG.debug("Streaming file: {} using buffer size: {}", normalizeSpace(fileItemInput.getName()), bufferSize);
for (int length; ((length = input.read(buffer)) > 0); ) {
output.write(buffer, 0, length);
}
@@ -242,6 +242,41 @@ class ContainerImpl implements Container {
return toArray(parameterInjectors);
}
/**
* Gets parameter injectors with nulls for optional dependencies.
*
* @param member to which the parameters belong
* @param annotations on the parameters
* @param parameterTypes parameter types
* @return injections
*/
<M extends AccessibleObject & Member> List<ParameterInjector<?>> getParametersInjectorsWithNulls(
M member,
Annotation[][] annotations,
Class<?>[] parameterTypes,
String defaultName
) throws MissingDependencyException {
final List<ParameterInjector<?>> parameterInjectors = new ArrayList<>();
final Iterator<Annotation[]> annotationsIterator = Arrays.asList(annotations).iterator();
for (Class<?> parameterType : parameterTypes) {
Inject annotation = findInject(annotationsIterator.next());
String name = annotation == null ? defaultName : annotation.value();
Key<?> key = Key.newInstance(parameterType, name);
try {
parameterInjectors.add(createParameterInjector(key, member));
} catch (MissingDependencyException e) {
if (annotation != null && annotation.required()) {
throw e;
} else {
parameterInjectors.add(createNullParameterInjector(key, member));
}
}
}
return parameterInjectors;
}
<T> ParameterInjector<T> createParameterInjector(Key<T> key, Member member) throws MissingDependencyException {
final InternalFactory<? extends T> factory = getFactory(key);
if (factory == null) {
@@ -252,6 +287,23 @@ class ContainerImpl implements Container {
return new ParameterInjector<>(externalContext, factory);
}
<T> ParameterInjector<T> createNullParameterInjector(Key<T> key, Member member) {
final InternalFactory<? extends T> factory = new InternalFactory<>() {
@Override
public T create(InternalContext context) {
return null;
}
@Override
public Class<? extends T> type() {
return key.getType();
}
};
final ExternalContext<T> externalContext = ExternalContext.newInstance(member, key, this);
return new ParameterInjector<>(externalContext, factory);
}
private ParameterInjector<?>[] toArray(List<ParameterInjector<?>> parameterInjections) {
return parameterInjections.toArray(new ParameterInjector[0]);
}
@@ -339,7 +391,7 @@ class ContainerImpl implements Container {
MissingDependencyException exception = null;
Inject inject = null;
ParameterInjector<?>[] parameters = null;
List<ParameterInjector<?>> parameters = null;
try {
inject = constructor.getAnnotation(Inject.class);
@@ -347,7 +399,7 @@ class ContainerImpl implements Container {
} catch (MissingDependencyException e) {
exception = e;
}
parameterInjectors = parameters;
parameterInjectors = parameters != null ? container.toArray(parameters) : null;
if (exception != null) {
if (inject != null && inject.required()) {
@@ -357,11 +409,11 @@ class ContainerImpl implements Container {
injectors = container.injectors.get(implementation);
}
ParameterInjector<?>[] constructParameterInjector(
List<ParameterInjector<?>> constructParameterInjector(
Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException {
return constructor.getParameterTypes().length == 0
? null // default constructor.
: container.getParametersInjectors(
: container.getParametersInjectorsWithNulls(
constructor,
constructor.getParameterAnnotations(),
constructor.getParameterTypes(),
@@ -18,11 +18,14 @@
*/
package org.apache.struts2.interceptor;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.config.entities.ExceptionMappingConfig;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.ognl.ThreadAllowlist;
import java.util.List;
import java.util.Map;
@@ -42,11 +45,11 @@ import java.util.Map;
* you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
* exception, even those caused by other interceptors.
* </p>
*
* <p>
* <!-- END SNIPPET: description -->
*
* <p><u>Interceptor parameters:</u></p>
*
* <p>
* <!-- START SNIPPET: parameters -->
*
* <ul>
@@ -64,11 +67,11 @@ import java.util.Map;
* The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
* and present a friendly webpage (with no stacktrace) to the end user.
* </p>
*
* <p>
* <!-- END SNIPPET: parameters -->
*
* <p><u>Extending the interceptor:</u></p>
*
* <p>
* <!-- START SNIPPET: extending -->
* <p>
* If you want to add custom handling for publishing the Exception, you may override
@@ -158,11 +161,17 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
private transient ThreadAllowlist threadAllowlist;
protected Logger categoryLogger;
protected boolean logEnabled = false;
protected String logCategory;
protected String logLevel;
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
public boolean isLogEnabled() {
return logEnabled;
@@ -173,20 +182,20 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
}
public String getLogCategory() {
return logCategory;
}
return logCategory;
}
public void setLogCategory(String logCatgory) {
this.logCategory = logCatgory;
}
public void setLogCategory(String logCategory) {
this.logCategory = logCategory;
}
public String getLogLevel() {
return logLevel;
}
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
@@ -200,13 +209,16 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
}
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
if (mappingConfig != null && mappingConfig.getResult()!=null) {
if (mappingConfig != null && mappingConfig.getResult() != null) {
Map<String, String> mappingParams = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
HttpParameters parameters = HttpParameters.create(mappingParams).build();
invocation.getInvocationContext().withParameters(parameters);
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
ExceptionHolder holder = new ExceptionHolder(e);
threadAllowlist.allowClass(holder.getClass());
threadAllowlist.allowClass(e.getClass());
publishException(invocation, holder);
} else {
throw e;
}
@@ -221,55 +233,45 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* @param e the exception to log.
*/
protected void handleLogging(Exception e) {
if (logCategory != null) {
if (categoryLogger == null) {
// init category logger
categoryLogger = LogManager.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
doLog(LOG, e);
}
if (logCategory != null) {
if (categoryLogger == null) {
// init category logger
categoryLogger = LogManager.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
doLog(LOG, e);
}
}
/**
* Performs the actual logging.
*
* @param logger the provided logger to use.
* @param e the exception to log.
* @param logger the provided logger to use.
* @param e the exception to log.
*/
protected void doLog(Logger logger, Exception e) {
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
}
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
}
if ("trace".equalsIgnoreCase(logLevel)) {
logger.trace(e.getMessage(), e);
} else if ("debug".equalsIgnoreCase(logLevel)) {
logger.debug(e.getMessage(), e);
} else if ("info".equalsIgnoreCase(logLevel)) {
logger.info(e.getMessage(), e);
} else if ("warn".equalsIgnoreCase(logLevel)) {
logger.warn(e.getMessage(), e);
} else if ("error".equalsIgnoreCase(logLevel)) {
logger.error(e.getMessage(), e);
} else if ("fatal".equalsIgnoreCase(logLevel)) {
logger.fatal(e.getMessage(), e);
} else {
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
Level level = Level.getLevel(logLevel);
if (level == null) {
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
logger.log(level, e.getMessage(), e);
}
/**
* Try to find appropriate {@link ExceptionMappingConfig} based on provided Throwable
*
* @param exceptionMappings list of defined exception mappings
* @param t caught exception
* @param t caught exception
* @return appropriate mapping or null
*/
protected ExceptionMappingConfig findMappingFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
ExceptionMappingConfig config = null;
ExceptionMappingConfig config = null;
// Check for specific exception mappings.
if (exceptionMappings != null) {
int deepest = Integer.MAX_VALUE;
@@ -288,15 +290,15 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* @param exceptionMapping the mapping classname
* @param t the cause
* @param exceptionMapping the mapping classname
* @param t the cause
* @return the depth, if not found -1 is returned.
*/
public int getDepth(String exceptionMapping, Throwable t) {
return getDepth(exceptionMapping, t.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
@@ -312,7 +314,7 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
* Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
* Subclasses may override this to customize publishing.
*
* @param invocation The invocation to publish Exception for.
* @param invocation The invocation to publish Exception for.
* @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
*/
protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
@@ -18,21 +18,23 @@
*/
package org.apache.struts2.interceptor.debugging;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.interceptor.AbstractInterceptor;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ClassUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.DispatcherConstants;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.interceptor.AbstractInterceptor;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.reflection.ReflectionProvider;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.freemarker.FreemarkerResult;
@@ -101,10 +103,10 @@ public class DebuggingInterceptor extends AbstractInterceptor {
private final String[] ignorePrefixes = new String[]{"org.apache.struts.", "org.apache.struts2.", "xwork."};
private final Set<String> ignoreKeys = Set.of(
DispatcherConstants.APPLICATION,
DispatcherConstants.SESSION,
DispatcherConstants.PARAMETERS,
DispatcherConstants.REQUEST
DispatcherConstants.APPLICATION,
DispatcherConstants.SESSION,
DispatcherConstants.PARAMETERS,
DispatcherConstants.REQUEST
);
private final static String XML_MODE = "xml";
@@ -126,6 +128,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
private boolean consoleEnabled = false;
private ReflectionProvider reflectionProvider;
private transient ThreadAllowlist threadAllowlist;
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String mode) {
@@ -142,6 +145,11 @@ public class DebuggingInterceptor extends AbstractInterceptor {
this.reflectionProvider = reflectionProvider;
}
@Inject
public void setThreadAllowlist(ThreadAllowlist threadAllowlist) {
this.threadAllowlist = threadAllowlist;
}
/*
* (non-Javadoc)
*
@@ -200,7 +208,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
res.setContentType("text/plain");
try (PrintWriter writer =
ServletActionContext.getResponse().getWriter()) {
ServletActionContext.getResponse().getWriter()) {
writer.print(stack.findValue(cmd));
} catch (IOException ex) {
LOG.warn("Interceptor in: {} mode has failed!", COMMAND_MODE, ex);
@@ -217,6 +225,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
String decorate = getParameter(DECORATE_PARAM);
ValueStack stack = ctx.getValueStack();
Object rootObject = stack.findValue(rootObjectExpression);
allowListClass(rootObject);
try (StringWriter writer = new StringWriter()) {
ObjectToHTMLWriter htmlWriter = new ObjectToHTMLWriter(writer);
@@ -228,8 +237,9 @@ public class DebuggingInterceptor extends AbstractInterceptor {
//on the first request, response can be decorated
//but we need plain text on the other ones
if ("false".equals(decorate))
if ("false".equals(decorate)) {
ServletActionContext.getRequest().setAttribute("decorator", "none");
}
FreemarkerResult result = new FreemarkerResult();
result.setFreemarkerManager(freemarkerManager);
@@ -239,7 +249,6 @@ public class DebuggingInterceptor extends AbstractInterceptor {
} catch (Exception ex) {
LOG.error("Unable to create debugging console", ex);
}
});
}
}
@@ -262,6 +271,14 @@ public class DebuggingInterceptor extends AbstractInterceptor {
}
}
private void allowListClass(Object o) {
if (o != null) {
threadAllowlist.allowClass(o.getClass());
ClassUtils.getAllSuperclasses(o.getClass()).forEach(threadAllowlist::allowClass);
ClassUtils.getAllInterfaces(o.getClass()).forEach(threadAllowlist::allowClass);
}
}
/**
* Gets a single string from the request parameters
*
@@ -277,12 +294,11 @@ public class DebuggingInterceptor extends AbstractInterceptor {
* Prints the current context to the response in XML format.
*/
protected void printContext() {
HttpServletResponse res = ServletActionContext.getResponse();
res.setContentType("text/xml");
HttpServletResponse response = ActionContext.getContext().getServletResponse();
response.setContentType("text/xml");
try {
PrettyPrintWriter writer = new PrettyPrintWriter(
ServletActionContext.getResponse().getWriter());
PrettyPrintWriter writer = new PrettyPrintWriter(response.getWriter());
printContext(writer);
writer.close();
} catch (IOException ex) {
@@ -311,6 +327,7 @@ public class DebuggingInterceptor extends AbstractInterceptor {
}
}
if (print) {
allowListClass(ctxMap.get(key));
serializeIt(ctxMap.get(key), key, writer, new ArrayList<>());
}
}
@@ -426,5 +443,3 @@ public class DebuggingInterceptor extends AbstractInterceptor {
return filter;
}
}
@@ -18,15 +18,13 @@
*/
package org.apache.struts2.ognl;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ProxyUtil;
import ognl.MemberAccess;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.ognl.ProviderAllowlist;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.ProxyUtil;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
@@ -40,6 +38,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import static java.text.MessageFormat.format;
import static java.util.Collections.emptySet;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
import static org.apache.struts2.util.ConfigParseUtil.toClassObjectsSet;
import static org.apache.struts2.util.ConfigParseUtil.toClassesSet;
import static org.apache.struts2.util.ConfigParseUtil.toNewClassesSet;
@@ -47,10 +49,6 @@ import static org.apache.struts2.util.ConfigParseUtil.toNewPackageNamesSet;
import static org.apache.struts2.util.ConfigParseUtil.toNewPatternsSet;
import static org.apache.struts2.util.ConfigParseUtil.toPackageNamesSet;
import static org.apache.struts2.util.DebugUtils.logWarningForFirstOccurrence;
import static java.text.MessageFormat.format;
import static java.util.Collections.emptySet;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
/**
* Allows access decisions to be made on the basis of whether a member is static or not.
@@ -143,6 +141,9 @@ public class SecurityMemberAccess implements MemberAccess {
public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
LOG.debug("Checking access for [target: {}, member: {}, property: {}]", target, member, propertyName);
if (member == null) {
throw new IllegalArgumentException("Member cannot be null!");
}
if (target != null) {
// Special case: Target is a Class object but not Class.class
if (Class.class.equals(target.getClass()) && !Class.class.equals(target)) {
@@ -211,7 +212,7 @@ public class SecurityMemberAccess implements MemberAccess {
return true;
}
if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
if (!disallowProxyObjectAccess && ProxyUtil.isProxy(target)) {
// If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
// classes/members. This allows the allowlist capability to continue working and offer some level of
// protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
@@ -36,7 +36,8 @@ public class DefaultExcludedPatternsChecker implements ExcludedPatternsChecker {
private static final Logger LOG = LogManager.getLogger(DefaultExcludedPatternsChecker.class);
public static final String[] EXCLUDED_PATTERNS = {
"(^|\\%\\{)((#?)(top(\\.|\\['|\\[\")|\\[\\d\\]\\.)?)(dojo|struts|session|request|response|application|servlet(Request|Response|Context)|parameters|context|_memberAccess)(\\.|\\[).*",
"(^|\\%\\{)(#?top\\.)[^\\s]*",
"(^|\\%\\{)((#?)(\\[\\d\\]\\.)?)(dojo|struts|session|request|response|application|servlet(Request|Response|Context)|parameters|context|_memberAccess)(\\.|\\[).*",
".*(^|\\.|\\[|\\'|\"|get)class(\\(\\.|\\[|\\'|\").*",
"actionErrors|actionMessages|fieldErrors"
};
@@ -38,7 +38,7 @@ public final class DebugUtils {
}
log.error(message);
if (action instanceof ValidationAware validationAware) {
validationAware.addActionError(message);
validationAware.addActionMessage(message);
}
}
@@ -18,12 +18,12 @@
*/
package org.apache.struts2.util;
import org.apache.struts2.ognl.DefaultOgnlCacheFactory;
import org.apache.struts2.ognl.OgnlCache;
import org.apache.struts2.ognl.OgnlCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.struts2.ognl.DefaultOgnlCacheFactory;
import org.apache.struts2.ognl.OgnlCache;
import org.apache.struts2.ognl.OgnlCacheFactory;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
@@ -81,6 +81,7 @@ public class ProxyUtil {
* @param object the object to check
*/
public static boolean isProxy(Object object) {
if (object == null) return false;
Class<?> clazz = object.getClass();
Boolean flag = isProxyCache.get(clazz);
if (flag != null) {
@@ -121,7 +122,7 @@ public class ProxyUtil {
*/
public static boolean isHibernateProxy(Object object) {
try {
return HibernateProxy.class.isAssignableFrom(object.getClass());
return object != null && HibernateProxy.class.isAssignableFrom(object.getClass());
} catch (NoClassDefFoundError ignored) {
return false;
}
@@ -29,6 +29,8 @@ import org.apache.struts2.dispatcher.AttributeMap;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.jsp.PageContext;
import static org.apache.struts2.dispatcher.DispatcherConstants.ATTRIBUTES;
public class TagUtils {
private static final Logger LOG = LogManager.getLogger(TagUtils.class);
@@ -46,8 +48,8 @@ public class TagUtils {
} else {
LOG.trace("Adds the current PageContext to ActionContext");
stack.getActionContext()
.withPageContext(pageContext)
.with("attr", new AttributeMap(stack.getContext()));
.withPageContext(pageContext)
.with(ATTRIBUTES, new AttributeMap(stack.getContext()));
}
return stack;
@@ -261,10 +261,12 @@ public abstract class AbstractUITag extends ComponentTagSupport implements Dynam
this.onchange = onchange;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltip(String tooltip) {
this.tooltip = tooltip;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipConfig(String tooltipConfig) {
this.tooltipConfig = tooltipConfig;
}
@@ -277,18 +279,22 @@ public abstract class AbstractUITag extends ComponentTagSupport implements Dynam
this.key = key;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setJavascriptTooltip(String javascriptTooltip) {
this.javascriptTooltip = javascriptTooltip;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipCssClass(String tooltipCssClass) {
this.tooltipCssClass = tooltipCssClass;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipDelay(String tooltipDelay) {
this.tooltipDelay = tooltipDelay;
}
@Deprecated(since = "7.0.1", forRemoval = true)
public void setTooltipIconPath(String tooltipIconPath) {
this.tooltipIconPath = tooltipIconPath;
}
@@ -19,8 +19,8 @@
*/
-->
<!DOCTYPE html>
<html>
<@s.style>
<html lang="en">
<style>
.debugTable {
border-style: solid;
border-width: 1px;
@@ -50,40 +50,40 @@
.emptyCollection {
background-color: #EEEEEE;
}
</@s.style>
</style>
<@s.script>
<script>
function expand(src, path) {
var baseUrl = location.href;
var i = baseUrl.indexOf("&object=");
let baseUrl = location.href;
const i = baseUrl.indexOf('&object=');
baseUrl = (i > 0 ? baseUrl.substring(0, i) : baseUrl) + "&object=" + path;
if (baseUrl.indexOf("decorate") < 0) {
baseUrl += "&decorate=false";
}
var request = new XMLHttpRequest();
const request = new XMLHttpRequest();
request.open('GET', baseUrl, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
var div = document.createElement("div");
const div = document.createElement('div');
console.log(this.responseText);
div.innerHTML = this.responseText;
src.parentNode.appendChild(div);
src.innerHTML = "Collapse";
var oldonclick = src.onclick;
const oldOnclick = src.onclick;
src.onclick = function() {
src.innerHTML = "Expand";
src.parentNode.removeChild(div);
src.onclick = oldonclick;
src.onclick = oldOnclick;
};
}
}
};
request.send();
}
</@s.script>
</script>
<body>
${debugHtml?no_esc}
@@ -23,21 +23,25 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="webconsole.css"/>
<script src="webconsole.js"></script>
<title>OGNL Console</title>
<link rel="stylesheet" type="text/css" href="webconsole.css"/>
<script src="webconsole.js"></script>
<title>OGNL Console</title>
</head>
<body>
<div id="shell">
<div class="wc-results" id="wc-result">
Welcome to the OGNL console!
<br/>
:-&gt;
</div>
<form onsubmit="return false" id="wc-form">
<input type="hidden" name="debug" value="command"/>
<input name="expression" onkeyup="keyEvent(event)" class="wc-command" id="wc-command" type="text"/>
</form>
<div class="wc-results" id="wc-result">
Welcome to the OGNL console!
<br/>
:-&gt;
</div>
<form onsubmit="return false" id="wc-form">
<input type="hidden" name="debug" value="command"/>
<input name="expression" class="wc-command" id="wc-command" type="text"/>
<script>
const input = document.getElementById("wc-command")
input.addEventListener("keyup", keyEvent);
</script>
</form>
</div>
</body>
</html>
@@ -28,7 +28,6 @@
<constant name="struts.excludedClasses"
value="
org.apache.struts2.ActionContext,
java.lang.Class,
java.lang.ClassLoader,
java.lang.InheritableThreadLocal,
@@ -43,7 +42,8 @@
java.lang.System,
java.lang.Thread,
java.lang.ThreadGroup,
java.lang.ThreadLocal
java.lang.ThreadLocal,
org.apache.struts2.ActionContext
"/>
<constant name="struts.devMode.excludedClasses"
@@ -74,15 +74,11 @@
<constant name="struts.excludedPackageNames"
value="
antlr.build,
org.apache.struts2.config,
org.apache.struts2.inject,
org.apache.struts2.ognl,
org.apache.struts2.security,
org.apache.struts2.util,
freemarker.core,
freemarker.ext.jsp,
freemarker.ext.rhino,
freemarker.template,
jakarta,
java.io,
java.net,
java.nio,
@@ -93,6 +89,11 @@
org.apache.commons.beanutils,
org.apache.commons.collections,
org.apache.jasper,
org.apache.struts2.config,
org.apache.struts2.inject,
org.apache.struts2.ognl,
org.apache.struts2.security,
org.apache.struts2.util,
org.apache.tomcat,
org.apache.velocity,
org.objectweb.asm,
@@ -106,15 +107,11 @@
<constant name="struts.devMode.excludedPackageNames"
value="
antlr.build,
org.apache.struts2.config,
org.apache.struts2.inject,
org.apache.struts2.ognl,
org.apache.struts2.security,
org.apache.struts2.util,
freemarker.core,
freemarker.ext.jsp,
freemarker.ext.rhino,
freemarker.template,
jakarta,
java.io,
java.net,
java.nio,
@@ -125,7 +122,11 @@
org.apache.commons.beanutils,
org.apache.commons.collections,
org.apache.jasper,
org.apache.struts2.config,
org.apache.struts2.inject,
org.apache.struts2.ognl,
org.apache.struts2.security,
org.apache.struts2.util,
org.apache.tomcat,
org.apache.velocity,
org.objectweb.asm,
@@ -18,8 +18,6 @@
*/
package org.apache.struts2;
import org.apache.struts2.ActionProxyFactory;
import org.apache.struts2.XWorkJUnit4TestCase;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.util.StrutsTestCaseHelper;
import org.apache.struts2.views.jsp.StrutsMockServletContext;
@@ -0,0 +1,88 @@
/*
* 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.components;
import org.apache.struts2.ActionContext;
import org.apache.struts2.dispatcher.DispatcherConstants;
import org.apache.struts2.views.jsp.AbstractTagTest;
import java.util.Map;
import static org.apache.struts2.components.UIBean.TEMPLATE_DIR;
import static org.apache.struts2.components.UIBean.THEME;
public class UIBeanTagTest extends AbstractTagTest {
private UIBean bean;
@Override
public void setUp() throws Exception {
super.setUp();
bean = new UIBean(stack, request, response) {
@Override
protected String getDefaultTemplate() {
return null;
}
};
}
public void testTemplateDir_ognlExpression() {
bean.setTemplateDir("%{testDir}");
stack.push(new Object() {
public String getTestDir() {
return "testValue";
}
});
assertEquals("testValue", bean.getTemplateDir());
}
public void testTemplateDir_attrMapFallback() {
ActionContext.of(context).getApplication().put(TEMPLATE_DIR, "applicationValue");
assertEquals("applicationValue", bean.getTemplateDir());
ActionContext.of(context).getSession().put(TEMPLATE_DIR, "sessionValue");
assertEquals("sessionValue", bean.getTemplateDir());
((Map<String, Object>) context.get(DispatcherConstants.REQUEST)).put(TEMPLATE_DIR, "requestValue");
assertEquals("requestValue", bean.getTemplateDir());
}
public void testTheme_ognlExpression() {
bean.setTheme("%{testTheme}");
stack.push(new Object() {
public String getTestTheme() {
return "testValue";
}
});
assertEquals("testValue", bean.getTheme());
}
public void testTheme_attrMapFallback() {
ActionContext.of(context).getApplication().put(THEME, "applicationValue");
assertEquals("applicationValue", bean.getTheme());
ActionContext.of(context).getSession().put(THEME, "sessionValue");
assertEquals("sessionValue", bean.getTheme());
((Map<String, Object>) context.get(DispatcherConstants.REQUEST)).put(THEME, "requestValue");
assertEquals("requestValue", bean.getTheme());
}
}
@@ -18,18 +18,6 @@
*/
package org.apache.struts2.dispatcher;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.struts2.StrutsStatics;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -41,6 +29,22 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.servlet.jsp.PageContext;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class AttributeMapTest {
@@ -361,4 +365,21 @@ public class AttributeMapTest {
assertEquals("value", value);
}
}
@Test
public void get_whenPageContextHasNoRequest() {
PageContext pageContext = mock(PageContext.class);
when(pageContext.getRequest()).thenReturn(null);
var req = new MockHttpServletRequest();
req.setAttribute("attr", "reqValue");
var attributeMap = new AttributeMap(Map.of(
StrutsStatics.PAGE_CONTEXT, pageContext,
DispatcherConstants.REQUEST, new RequestMap(req)
));
assertEquals("reqValue", attributeMap.get("attr"));
verify(pageContext, never()).findAttribute(anyString());
}
}
@@ -344,6 +344,9 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase {
req.setContentType("Multipart/Form-Data ; boundary=---------------------------207103069210263;charset=UTF-16LE");
assertTrue(dispatcher.isMultipartRequest(req));
req.setContentType("multipart/form-data; boundary=\"----=_Part_38_1092302434.1734807780737\"");
assertTrue(dispatcher.isMultipartRequest(req));
}
@Test
@@ -48,7 +48,7 @@ public class JakartaStreamMultiPartRequestTest extends AbstractMultiPartRequestT
// when
multiPart.setMaxSizeOfFiles("10");
multiPart.parse(mockRequest, tempDir.toString());
multiPart.parse(mockRequest, tempDir);
// then
assertThat(multiPart.uploadedFiles)
@@ -28,7 +28,9 @@ import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -49,6 +51,7 @@ public class ContainerImplTest {
ContainerBuilder cb = new ContainerBuilder();
cb.constant("methodCheck.name", "Lukasz");
cb.constant("fieldCheck.name", "Lukasz");
cb.constant("constructorCheck.name", "Lukasz");
cb.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
cb.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
cb.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
@@ -65,15 +68,43 @@ public class ContainerImplTest {
}
@Test
public void fieldInjector() throws Exception {
public void fieldInjector() {
FieldCheck fieldCheck = new FieldCheck();
c.inject(fieldCheck);
assertEquals(fieldCheck.getName(), "Lukasz");
assertEquals("Lukasz", fieldCheck.getName());
}
@Test
public void methodInjector() throws Exception {
c.inject(new MethodCheck());
public void methodInjector() {
MethodCheck methodCheck = new MethodCheck();
c.inject(methodCheck);
assertEquals("Lukasz", methodCheck.getName());
}
@Test
public void constructorInjector() {
ConstructorCheck constructorCheck = c.inject(ConstructorCheck.class);
assertEquals("Lukasz", constructorCheck.getName());
}
@Test
public void optionalConstructorInjector() {
OptionalConstructorCheck constructorCheck = c.inject(OptionalConstructorCheck.class);
assertNull(constructorCheck.getName());
}
@Test
public void requiredOptionalConstructorInjector() {
RequiredOptionalConstructorCheck constructorCheck = c.inject(RequiredOptionalConstructorCheck.class);
assertNotNull(constructorCheck.getExistingName());
assertNull(constructorCheck.getNonExitingName());
}
@Test
public void optionalRequiredConstructorInjector() {
OptionalRequiredConstructorCheck constructorCheck = c.inject(OptionalRequiredConstructorCheck.class);
assertNull(constructorCheck.getNonExitingName());
assertNotNull(constructorCheck.getExistingName());
}
/**
@@ -92,7 +123,7 @@ public class ContainerImplTest {
* Inject values into method under SecurityManager
*/
@Test
public void testMethodInjectorWithSecurityEnabled() throws Exception {
public void testMethodInjectorWithSecurityEnabled() {
assumeTrue(SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_20));
System.setSecurityManager(new TestSecurityManager());
assertThrows(DependencyException.class, () -> c.inject(new MethodCheck()));
@@ -101,7 +132,7 @@ public class ContainerImplTest {
}
@Test
public void testEarlyInitializable() throws Exception {
public void testEarlyInitializable() {
assertTrue("should being initialized already", EarlyInitializableBean.initializedEarly);
EarlyInitializableCheck earlyInitializableCheck = new EarlyInitializableCheck();
@@ -148,22 +179,19 @@ public class ContainerImplTest {
final InitializableCheck initializableCheck3 = new InitializableCheck();
final TestScopeStrategy testScopeStrategy = new TestScopeStrategy();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ContainerBuilder cb2 = new ContainerBuilder();
cb2.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
cb2.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
cb2.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
cb2.factory(Initializable.class, "prototypeInitializable", InitializableBean.class, Scope.PROTOTYPE);
cb2.factory(Initializable.class, "requestInitializable", InitializableBean.class, Scope.REQUEST);
cb2.factory(Initializable.class, "sessionInitializable", InitializableBean.class, Scope.SESSION);
cb2.factory(Initializable.class, "threadInitializable", InitializableBean.class, Scope.THREAD);
cb2.factory(Initializable.class, "wizardInitializable", InitializableBean.class, Scope.WIZARD);
Container c2 = cb2.create(false);
c2.setScopeStrategy(testScopeStrategy);
c2.inject(initializableCheck3);
}
Thread thread = new Thread(() -> {
ContainerBuilder cb2 = new ContainerBuilder();
cb2.factory(EarlyInitializable.class, EarlyInitializableBean.class, Scope.SINGLETON);
cb2.factory(Initializable.class, InitializableBean.class, Scope.SINGLETON);
cb2.factory(EarlyInitializable.class, "prototypeEarlyInitializable", EarlyInitializableBean.class, Scope.PROTOTYPE);
cb2.factory(Initializable.class, "prototypeInitializable", InitializableBean.class, Scope.PROTOTYPE);
cb2.factory(Initializable.class, "requestInitializable", InitializableBean.class, Scope.REQUEST);
cb2.factory(Initializable.class, "sessionInitializable", InitializableBean.class, Scope.SESSION);
cb2.factory(Initializable.class, "threadInitializable", InitializableBean.class, Scope.THREAD);
cb2.factory(Initializable.class, "wizardInitializable", InitializableBean.class, Scope.WIZARD);
Container c2 = cb2.create(false);
c2.setScopeStrategy(testScopeStrategy);
c2.inject(initializableCheck3);
});
thread.run();
thread.join();
@@ -205,6 +233,76 @@ public class ContainerImplTest {
}
public static class ConstructorCheck {
private String name;
@Inject("constructorCheck.name")
public ConstructorCheck(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static class OptionalConstructorCheck {
private String name;
@Inject(value = "nonExistingConstant", required = false)
public OptionalConstructorCheck(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static class RequiredOptionalConstructorCheck {
private final String existingName;
private final String nonExitingName;
@Inject(required = false)
public RequiredOptionalConstructorCheck(
@Inject("constructorCheck.name") String existingName,
@Inject(value = "nonExistingConstant", required = false) String nonExitingName
) {
this.existingName = existingName;
this.nonExitingName = nonExitingName;
}
public String getExistingName() {
return existingName;
}
public String getNonExitingName() {
return nonExitingName;
}
}
public static class OptionalRequiredConstructorCheck {
private final String existingName;
private final String nonExitingName;
@Inject(required = false)
public OptionalRequiredConstructorCheck(
@Inject(value = "nonExistingConstant", required = false) String nonExitingName,
@Inject("constructorCheck.name") String existingName
) {
this.existingName = existingName;
this.nonExitingName = nonExitingName;
}
public String getExistingName() {
return existingName;
}
public String getNonExitingName() {
return nonExitingName;
}
}
class InitializableCheck {
private Initializable initializable;
@@ -549,6 +549,7 @@ public class ActionFileUploadInterceptorTest extends StrutsInternalTestCase {
jak.setMaxFiles(String.valueOf(maxfiles));
jak.setMaxStringLength(String.valueOf(maxStringLength));
jak.setDefaultEncoding(StandardCharsets.UTF_8.name());
return new MultiPartRequestWrapper(jak, request, tempDir.getAbsolutePath(), new DefaultLocaleProvider());
}
@@ -19,16 +19,17 @@
package org.apache.struts2.interceptor;
import com.mockobjects.dynamic.Mock;
import org.apache.struts2.action.Action;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ActionProxy;
import org.apache.struts2.StrutsException;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.action.Action;
import org.apache.struts2.config.entities.ActionConfig;
import org.apache.struts2.config.entities.ExceptionMappingConfig;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.validator.ValidationException;
import org.apache.struts2.StrutsException;
/**
* Unit test for ExceptionMappingInterceptor.
@@ -52,7 +53,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
String result = interceptor.intercept(invocation);
assertNotNull(stack.findValue("exception"));
assertEquals(stack.findValue("exception"), exception);
assertEquals(result, "spooky");
assertEquals("spooky", result);
ExceptionHolder holder = (ExceptionHolder) stack.getRoot().get(0); // is on top of the root
assertNotNull(holder.getExceptionStack()); // to invoke the method for unit test
}
@@ -67,7 +68,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
String result = interceptor.intercept(invocation);
assertNotNull(stack.findValue("exception"));
assertEquals(stack.findValue("exception"), exception);
assertEquals(result, "throwable");
assertEquals("throwable", result);
}
public void testNoThrownException() throws Exception {
@@ -77,7 +78,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.expectAndReturn("invoke", Action.SUCCESS);
mockInvocation.matchAndReturn("getAction", action.proxy());
String result = interceptor.intercept(invocation);
assertEquals(result, Action.SUCCESS);
assertEquals(Action.SUCCESS, result);
assertNull(stack.findValue("exception"));
}
@@ -106,7 +107,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogEnabled(true);
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -123,8 +124,8 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -141,9 +142,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("fatal");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("fatal");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -164,9 +165,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("error");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("error");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -183,9 +184,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("warn");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("warn");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -202,9 +203,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("info");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("info");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -221,9 +222,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("debug");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("debug");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -240,9 +241,9 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("trace");
interceptor.setLogEnabled(true);
interceptor.setLogCategory("showcase.unhandled");
interceptor.setLogLevel("trace");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (Exception e) {
@@ -259,12 +260,12 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.matchAndReturn("getAction", action.proxy());
try {
interceptor.setLogEnabled(true);
interceptor.setLogLevel("xxx");
interceptor.setLogEnabled(true);
interceptor.setLogLevel("xxx");
interceptor.intercept(invocation);
fail("Should not have reached this point.");
} catch (IllegalArgumentException e) {
// success
// success
}
}
@@ -296,6 +297,7 @@ public class ExceptionMappingInterceptorTest extends XWorkTestCase {
mockInvocation.expectAndReturn("getStack", stack);
mockInvocation.expectAndReturn("getInvocationContext", ActionContext.of().bind());
interceptor = new ExceptionMappingInterceptor();
interceptor.setThreadAllowlist(new ThreadAllowlist());
interceptor.init();
}
@@ -0,0 +1,569 @@
/*
* 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.debugging;
import org.apache.struts2.ActionContext;
import org.apache.struts2.StrutsJUnit4InternalTestCase;
import org.apache.struts2.TestAction;
import org.apache.struts2.dispatcher.DispatcherConstants;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.SessionMap;
import org.apache.struts2.mock.MockActionInvocation;
import org.apache.struts2.ognl.ThreadAllowlist;
import org.apache.struts2.util.ValueStack;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.assertj.core.util.Maps;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class DebuggingInterceptorTest extends StrutsJUnit4InternalTestCase {
private DebuggingInterceptor interceptor;
private MockActionInvocation invocation;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private ActionContext context;
private TestAction action;
@Test
public void noDevMode() throws Exception {
interceptor.intercept(invocation);
assertThat(invocation.getResultCode()).isEqualTo("mock");
assertThat(response.getContentAsString()).isEmpty();
}
@Test
public void debugXml() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "xml")).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<debug>
<parameters/>
<context/>
<request/>
<session/>
<valueStack>
<value>
<action>
<actionErrors/>
<actionMessages/>
<class>class org.apache.struts2.TestAction</class>
<fieldErrors/>
<locale>
<ISO3Country>USA</ISO3Country>
<ISO3Language>eng</ISO3Language>
<class>class java.util.Locale</class>
<country>US</country>
<displayCountry>United States</displayCountry>
<displayLanguage>English</displayLanguage>
<displayName>English (United States)</displayName>
<displayScript></displayScript>
<displayVariant></displayVariant>
<extensionKeys/>
<language>en</language>
<script></script>
<unicodeLocaleAttributes/>
<unicodeLocaleKeys/>
<variant></variant>
</locale>
<status>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>completed</displayName>
<name>COMPLETED</name>
</status>
<statusList>
<value>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>init</displayName>
<name>INIT</name>
</value>
<value>
<class>class org.apache.struts2.SomeEnum</class>
<declaringClass>class org.apache.struts2.SomeEnum</declaringClass>
<displayName>completed</displayName>
<name>COMPLETED</name>
</value>
</statusList>
<texts>
<baseBundleName>org.apache.struts2.TestAction</baseBundleName>
<class>class java.util.PropertyResourceBundle</class>
<keys>
<class>class sun.util.ResourceBundleEnumeration</class>
</keys>
<locale>
<ISO3Country></ISO3Country>
<ISO3Language></ISO3Language>
<class>class java.util.Locale</class>
<country></country>
<displayCountry></displayCountry>
<displayLanguage></displayLanguage>
<displayName></displayName>
<displayScript></displayScript>
<displayVariant></displayVariant>
<extensionKeys/>
<language></language>
<script></script>
<unicodeLocaleAttributes/>
<unicodeLocaleKeys/>
<variant></variant>
</locale>
</texts>
</action>
<org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY></org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY>
</value>
<value>
<class>class org.apache.struts2.text.DefaultTextProvider</class>
</value>
</valueStack>
</debug>
""");
}
@Test
public void debugXmlWithConsole() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "console")).build());
interceptor.setEnableXmlWithConsole(true);
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html>
<head>
<script>
var baseUrl = "/static";
window.open(baseUrl+"/webconsole.html", 'OGNL Console','width=500,height=450,status=no,toolbar=no,menubar=no');
</script>
</head>
<body>
<pre>
&amp;lt;debug&amp;gt;
&amp;lt;parameters/&amp;gt;
&amp;lt;context/&amp;gt;
&amp;lt;request/&amp;gt;
&amp;lt;session/&amp;gt;
&amp;lt;valueStack&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;action&amp;gt;
&amp;lt;actionErrors/&amp;gt;
&amp;lt;actionMessages/&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.TestAction&amp;lt;/class&amp;gt;
&amp;lt;fieldErrors/&amp;gt;
&amp;lt;locale&amp;gt;
&amp;lt;ISO3Country&amp;gt;USA&amp;lt;/ISO3Country&amp;gt;
&amp;lt;ISO3Language&amp;gt;eng&amp;lt;/ISO3Language&amp;gt;
&amp;lt;class&amp;gt;class java.util.Locale&amp;lt;/class&amp;gt;
&amp;lt;country&amp;gt;US&amp;lt;/country&amp;gt;
&amp;lt;displayCountry&amp;gt;United States&amp;lt;/displayCountry&amp;gt;
&amp;lt;displayLanguage&amp;gt;English&amp;lt;/displayLanguage&amp;gt;
&amp;lt;displayName&amp;gt;English (United States)&amp;lt;/displayName&amp;gt;
&amp;lt;displayScript&amp;gt;&amp;lt;/displayScript&amp;gt;
&amp;lt;displayVariant&amp;gt;&amp;lt;/displayVariant&amp;gt;
&amp;lt;extensionKeys/&amp;gt;
&amp;lt;language&amp;gt;en&amp;lt;/language&amp;gt;
&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;unicodeLocaleAttributes/&amp;gt;
&amp;lt;unicodeLocaleKeys/&amp;gt;
&amp;lt;variant&amp;gt;&amp;lt;/variant&amp;gt;
&amp;lt;/locale&amp;gt;
&amp;lt;status&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;completed&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;COMPLETED&amp;lt;/name&amp;gt;
&amp;lt;/status&amp;gt;
&amp;lt;statusList&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;init&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;INIT&amp;lt;/name&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/class&amp;gt;
&amp;lt;declaringClass&amp;gt;class org.apache.struts2.SomeEnum&amp;lt;/declaringClass&amp;gt;
&amp;lt;displayName&amp;gt;completed&amp;lt;/displayName&amp;gt;
&amp;lt;name&amp;gt;COMPLETED&amp;lt;/name&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;/statusList&amp;gt;
&amp;lt;texts&amp;gt;
&amp;lt;baseBundleName&amp;gt;org.apache.struts2.TestAction&amp;lt;/baseBundleName&amp;gt;
&amp;lt;class&amp;gt;class java.util.PropertyResourceBundle&amp;lt;/class&amp;gt;
&amp;lt;keys&amp;gt;
&amp;lt;class&amp;gt;class sun.util.ResourceBundleEnumeration&amp;lt;/class&amp;gt;
&amp;lt;/keys&amp;gt;
&amp;lt;locale&amp;gt;
&amp;lt;ISO3Country&amp;gt;&amp;lt;/ISO3Country&amp;gt;
&amp;lt;ISO3Language&amp;gt;&amp;lt;/ISO3Language&amp;gt;
&amp;lt;class&amp;gt;class java.util.Locale&amp;lt;/class&amp;gt;
&amp;lt;country&amp;gt;&amp;lt;/country&amp;gt;
&amp;lt;displayCountry&amp;gt;&amp;lt;/displayCountry&amp;gt;
&amp;lt;displayLanguage&amp;gt;&amp;lt;/displayLanguage&amp;gt;
&amp;lt;displayName&amp;gt;&amp;lt;/displayName&amp;gt;
&amp;lt;displayScript&amp;gt;&amp;lt;/displayScript&amp;gt;
&amp;lt;displayVariant&amp;gt;&amp;lt;/displayVariant&amp;gt;
&amp;lt;extensionKeys/&amp;gt;
&amp;lt;language&amp;gt;&amp;lt;/language&amp;gt;
&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;unicodeLocaleAttributes/&amp;gt;
&amp;lt;unicodeLocaleKeys/&amp;gt;
&amp;lt;variant&amp;gt;&amp;lt;/variant&amp;gt;
&amp;lt;/locale&amp;gt;
&amp;lt;/texts&amp;gt;
&amp;lt;/action&amp;gt;
&amp;lt;org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY&amp;gt;&amp;lt;/org.apache.struts2.util.OgnlValueStack.MAP_IDENTIFIER_KEY&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;value&amp;gt;
&amp;lt;class&amp;gt;class org.apache.struts2.text.DefaultTextProvider&amp;lt;/class&amp;gt;
&amp;lt;/value&amp;gt;
&amp;lt;/valueStack&amp;gt;
&amp;lt;/debug&amp;gt;
</pre>
</body>
</html>
""");
}
@Test
public void debugConsole() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "console")).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html>
<head>
<script>
var baseUrl = "/static";
window.open(baseUrl+"/webconsole.html", 'OGNL Console','width=500,height=450,status=no,toolbar=no,menubar=no');
</script>
</head>
<body>
<pre>
</pre>
</body>
</html>
""");
}
@Test
public void debugCommand() throws Exception {
interceptor.setDevMode("true");
Map<String, Object> params = new HashMap<>() {{
put("debug", "command");
put("expression", "1+1");
}};
context.withParameters(HttpParameters.create(params).build());
interceptor.intercept(invocation);
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("2");
}
@Test
public void debugBrowser() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "browser")).build());
interceptor.intercept(invocation);
invocation.invoke();
assertThat(response.getContentAsString()).isEqualToIgnoringWhitespace("""
<!DOCTYPE html>
<html lang="en">
<style>
.debugTable {
border-style: solid;
border-width: 1px;
}
.debugTable td {
border-style: solid;
border-width: 1px;
}
.nameColumn {
background-color:#CCDDFF;
}
.valueColumn {
background-color: #CCFFCC;
}
.nullValue {
background-color: #FF0000;
}
.typeColumn {
background-color: white;
}
.emptyCollection {
background-color: #EEEEEE;
}
</style>
<script>
function expand(src, path) {
let baseUrl = location.href;
const i = baseUrl.indexOf('&object=');
baseUrl = (i > 0 ? baseUrl.substring(0, i) : baseUrl) + "&object=" + path;
if (baseUrl.indexOf("decorate") < 0) {
baseUrl += "&decorate=false";
}
const request = new XMLHttpRequest();
request.open('GET', baseUrl, true);
request.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
const div = document.createElement('div');
console.log(this.responseText);
div.innerHTML = this.responseText;
src.parentNode.appendChild(div);
src.innerHTML = "Collapse";
const oldOnclick = src.onclick;
src.onclick = function() {
src.innerHTML = "Expand";
src.parentNode.removeChild(div);
src.onclick = oldOnclick;
};
}
}
};
request.send();
}
</script>
<body>
<table class="debugTable">
<tr>
<td class="nameColumn">container</td>
<td class="valueColumn">There is no read method for container</td>
<td class="typeColumn">java.lang.String</td>
</tr>
<tr>
<td class="nameColumn">foo</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">intList</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">locale</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;locale&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.Locale</td>
</tr>
<tr>
<td class="nameColumn">result</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">collection2</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">someBool</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">array</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">fooInt</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">id</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">map</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">actionErrors</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedList</td>
</tr>
<tr>
<td class="nameColumn">objectArray</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">fieldErrors</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedHashMap</td>
</tr>
<tr>
<td class="nameColumn">collection</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">floatNumber</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">list</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">enumList</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">actionMessages</td>
<td class="emptyCollection">empty</td>
<td class="typeColumn">java.util.LinkedList</td>
</tr>
<tr>
<td class="nameColumn">statusList</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;statusList&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.Arrays$ArrayList</td>
</tr>
<tr>
<td class="nameColumn">texts</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;texts&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">java.util.PropertyResourceBundle</td>
</tr>
<tr>
<td class="nameColumn">list3</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">list2</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">user</td>
<td class="nullValue">null</td>
<td class="nullValue">unknown</td>
</tr>
<tr>
<td class="nameColumn">status</td>
<td class="valueColumn">
<a onclick="expand(this, 'action[&quot;status&quot;]')" href="javascript://nop/">Expand</a>
</td>
<td class="typeColumn">org.apache.struts2.SomeEnum</td>
</tr>
</table>
</body>
</html>
""");
}
@Test
public void allowlist() throws Exception {
interceptor.setDevMode("true");
context.withParameters(HttpParameters.create(Maps.newHashMap("debug", "browser")).build());
assertThat(container.getInstance(ThreadAllowlist.class))
.extracting(ThreadAllowlist::getAllowlist).asInstanceOf(InstanceOfAssertFactories.SET)
.isEmpty();
interceptor.intercept(invocation);
invocation.invoke();
assertThat(container.getInstance(ThreadAllowlist.class))
.extracting(ThreadAllowlist::getAllowlist).asInstanceOf(InstanceOfAssertFactories.SET)
.contains(
org.apache.struts2.interceptor.ValidationAware.class,
org.apache.struts2.Validateable.class,
org.apache.struts2.action.Action.class,
org.apache.struts2.text.TextProvider.class,
org.apache.struts2.ActionSupport.class,
org.apache.struts2.locale.LocaleProvider.class,
org.apache.struts2.TestAction.class
);
}
@Before
public void before() {
request = new MockHttpServletRequest();
request.setSession(new MockHttpSession());
response = new MockHttpServletResponse();
ValueStack valueStack = dispatcher.getValueStackFactory().createValueStack();
context = valueStack.getActionContext()
.withServletContext(servletContext)
.withServletRequest(request)
.withServletResponse(response)
.withSession(new SessionMap(request))
.with(DispatcherConstants.REQUEST, new RequestMap(request));
interceptor = container.inject(DebuggingInterceptor.class);
interceptor.init();
invocation = new MockActionInvocation();
invocation.setResultCode("mock");
invocation.setInvocationContext(context);
action = new TestAction();
invocation.setAction(action);
invocation.setStack(valueStack);
valueStack.set("action", invocation.getAction());
context = context.withActionInvocation(invocation).bind();
}
}
@@ -117,9 +117,9 @@ public class ParametersInterceptorTest extends XWorkTestCase {
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(3, action.getActionErrors().size());
assertEquals(3, action.getActionMessages().size());
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
List<String> actionErrors = new ArrayList<>(action.getActionMessages());
String msg1 = actionErrors.get(0);
String msg2 = actionErrors.get(1);
@@ -204,9 +204,9 @@ public class ParametersInterceptorTest extends XWorkTestCase {
pi.applyParameters(action, vs, HttpParameters.create(params).build());
// then
assertEquals(3, action.getActionErrors().size());
assertEquals(3, action.getActionMessages().size());
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
List<String> actionErrors = new ArrayList<>(action.getActionMessages());
String msg1 = actionErrors.get(0);
String msg2 = actionErrors.get(1);
String msg3 = actionErrors.get(2);
@@ -586,7 +586,7 @@ 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 actionError = "" + ((SimpleAction) proxy.getAction()).getActionErrors().toArray()[0];
final String actionError = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
assertTrue(actionError.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
}
@@ -18,17 +18,26 @@
*/
package org.apache.struts2.ognl;
import ognl.OgnlException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.struts2.SimpleAction;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.TestBean;
import org.apache.struts2.text.TextProvider;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.DefaultPropertiesProvider;
import org.apache.struts2.conversion.impl.ConversionData;
import org.apache.struts2.conversion.impl.XWorkConverter;
import org.apache.struts2.inject.ContainerBuilder;
import org.apache.struts2.ognl.accessor.RootAccessor;
import org.apache.struts2.test.StubConfigurationProvider;
import org.apache.struts2.test.TestBean2;
import org.apache.struts2.text.TextProvider;
import org.apache.struts2.util.Bar;
import org.apache.struts2.util.BarJunior;
import org.apache.struts2.util.Cat;
@@ -37,15 +46,6 @@ import org.apache.struts2.util.Foo;
import org.apache.struts2.util.ValueStackFactory;
import org.apache.struts2.util.location.LocatableProperties;
import org.apache.struts2.util.reflection.ReflectionContextState;
import ognl.OgnlException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import org.apache.struts2.config.DefaultPropertiesProvider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -1234,6 +1234,33 @@ public class OgnlValueStackTest extends XWorkTestCase {
assertNull("accessed private field (result not null) ?", accessedValue);
}
public void testFindValueWithConstructorAndProxyChecks() {
loadButSet(Map.of(
StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Boolean.TRUE.toString(),
StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Boolean.TRUE.toString()));
refreshContainerFields();
String value = "test";
String ognlResult = (String) vs.findValue(
"new org.apache.struts2.ognl.OgnlValueStackTest$ValueHolder('" + value + "').value", String.class);
assertEquals(value, ognlResult);
}
@SuppressWarnings({"unused", "ClassCanBeRecord"})
public static class ValueHolder {
// See testFindValueWithConstructorAndProxyChecks
private final String value;
public ValueHolder(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
static class BadJavaBean {
private int count;
private int count2;
@@ -24,34 +24,60 @@ import java.util.HashMap;
public class WildcardHelperTest extends XWorkTestCase {
public void testMatch() {
private WildcardHelper wildcardHelper;
WildcardHelper wild = new WildcardHelper();
HashMap<String, String> matchedPatterns = new HashMap<>();
int[] pattern = wild.compilePattern("wes-rules");
assertEquals(wild.match(matchedPatterns,"wes-rules", pattern), true);
assertEquals(wild.match(matchedPatterns, "rules-wes", pattern), false);
@Override
public void setUp() throws Exception {
super.setUp();
pattern = wild.compilePattern("wes-*");
assertEquals(wild.match(matchedPatterns,"wes-rules", pattern), true);
assertEquals("rules".equals(matchedPatterns.get("1")), true);
assertEquals(wild.match(matchedPatterns, "rules-wes", pattern), false);
wildcardHelper = new WildcardHelper();
}
pattern = wild.compilePattern("path/**/file");
assertEquals(wild.match(matchedPatterns, "path/to/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), true);
assertEquals("to/another/location/of".equals(matchedPatterns.get("1")), true);
public void testMatch() {
HashMap<String, String> matchedPatterns = new HashMap<>();
int[] pattern = wildcardHelper.compilePattern("wes-rules");
assertEquals(wildcardHelper.match(matchedPatterns, "wes-rules", pattern), true);
assertEquals(wildcardHelper.match(matchedPatterns, "rules-wes", pattern), false);
pattern = wild.compilePattern("path/*/file");
assertEquals(wild.match(matchedPatterns, "path/to/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), false);
pattern = wildcardHelper.compilePattern("wes-*");
assertEquals(wildcardHelper.match(matchedPatterns, "wes-rules", pattern), true);
assertEquals("rules".equals(matchedPatterns.get("1")), true);
assertEquals(wildcardHelper.match(matchedPatterns, "rules-wes", pattern), false);
pattern = wild.compilePattern("path/*/another/**/file");
assertEquals(wild.match(matchedPatterns, "path/to/another/location/of/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals("location/of".equals(matchedPatterns.get("2")), true);
}
pattern = wildcardHelper.compilePattern("path/**/file");
assertEquals(wildcardHelper.match(matchedPatterns, "path/to/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals(wildcardHelper.match(matchedPatterns, "path/to/another/location/of/file", pattern), true);
assertEquals("to/another/location/of".equals(matchedPatterns.get("1")), true);
pattern = wildcardHelper.compilePattern("path/*/file");
assertEquals(wildcardHelper.match(matchedPatterns, "path/to/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals(wildcardHelper.match(matchedPatterns, "path/to/another/location/of/file", pattern), false);
pattern = wildcardHelper.compilePattern("path/*/another/**/file");
assertEquals(wildcardHelper.match(matchedPatterns, "path/to/another/location/of/file", pattern), true);
assertEquals("to".equals(matchedPatterns.get("1")), true);
assertEquals("location/of".equals(matchedPatterns.get("2")), true);
}
public void testMatchStrutsPackages() {
// given
HashMap<String, String> matchedPatterns = new HashMap<>();
int[] pattern = wildcardHelper.compilePattern("org.apache.struts2.*");
// when & then
assertTrue(wildcardHelper.match(matchedPatterns, "org.apache.struts2.XWorkTestCase", pattern));
assertEquals("org.apache.struts2.XWorkTestCase", matchedPatterns.get("0"));
assertEquals("XWorkTestCase", matchedPatterns.get("1"));
assertTrue(wildcardHelper.match(matchedPatterns, "org.apache.struts2.core.SomeClass", pattern));
assertEquals("org.apache.struts2.core.SomeClass", matchedPatterns.get("0"));
assertEquals("core.SomeClass", matchedPatterns.get("1"));
assertTrue(wildcardHelper.match(matchedPatterns, "org.apache.struts2.", pattern));
assertEquals("org.apache.struts2.", matchedPatterns.get("0"));
assertEquals("", matchedPatterns.get("1"));
}
}
@@ -18,13 +18,12 @@
*/
package org.apache.struts2.views.jsp.ui;
import org.apache.struts2.config.ConfigurationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.views.jsp.AbstractUITagTest;
import java.util.HashMap;
import java.util.Collections;
import java.util.Map;
/**
@@ -59,7 +58,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -82,7 +81,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -98,7 +97,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -116,7 +115,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
}
@@ -135,7 +134,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -158,7 +157,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -177,7 +176,7 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPageContext(pageContext);
// DebugTag has no additional state, so it compares as equal with the default tag clear state as well.
assertTrue("Tag state after doEndTag() under default tag clear state is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
@@ -198,16 +197,14 @@ public class DebugTagTest extends AbstractUITagTest {
freshTag.setPerformClearTagStateForTagPoolingServers(true);
freshTag.setPageContext(pageContext);
assertTrue("Tag state after doEndTag() and explicit tag state clearing is inequal to new Tag with pageContext/parent set. " +
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
"May indicate that clearTagStateForTagPoolingServers() calls are not working properly.",
strutsBodyTagsAreReflectionEqual(tag, freshTag));
PrepareOperations.clearDevModeOverride(); // Clear DevMode override. Avoid ThreadLocal side-effects if test thread re-used.
}
private void setDevMode(final boolean devMode) {
setStrutsConstant(new HashMap<String, String>() {{
put(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode));
}});
setStrutsConstant(Collections.singletonMap(StrutsConstants.STRUTS_DEVMODE, Boolean.toString(devMode)));
}
/**
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jakarta</artifactId>
<packaging>pom</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-tools-view-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-async-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-cdi-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-config-browser-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-convention-plugin</artifactId>
@@ -18,26 +18,27 @@
*/
package org.apache.struts2.convention;
import org.apache.struts2.action.Action;
import org.apache.struts2.ActionContext;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.finder.ClassLoaderInterface;
import org.apache.struts2.util.finder.ClassLoaderInterfaceDelegate;
import org.apache.struts2.util.finder.ResourceFinder;
import org.apache.struts2.util.finder.Test;
import jakarta.servlet.ServletContext;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ActionContext;
import org.apache.struts2.action.Action;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import org.apache.struts2.inject.Container;
import org.apache.struts2.inject.Inject;
import org.apache.struts2.util.TextParseUtil;
import org.apache.struts2.util.finder.ClassLoaderInterface;
import org.apache.struts2.util.finder.ClassLoaderInterfaceDelegate;
import org.apache.struts2.util.finder.ResourceFinder;
import org.apache.struts2.util.finder.Test;
import java.io.IOException;
import java.net.URL;
@@ -123,23 +124,24 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
/**
* Constructs the SimpleResultMapBuilder using the given result location.
*
* @param servletContext The ServletContext for finding the resources of the web application.
* @param container The Xwork container
* @param relativeResultTypes The list of result types that can have locations that are relative
* and the result location (which is the resultPath plus the namespace) prepended to them.
* @param servletContext The ServletContext for finding the resources of the web application.
* @param container The Xwork container
* @param relativeResultTypes The list of result types that can have locations that are relative
* and the result location (which is the resultPath plus the namespace) prepended to them.
*/
@Inject
public DefaultResultMapBuilder(ServletContext servletContext, Container container,
@Inject(ConventionConstants.CONVENTION_RELATIVE_RESULT_TYPES) String relativeResultTypes) {
@Inject(ConventionConstants.CONVENTION_RELATIVE_RESULT_TYPES) String relativeResultTypes) {
this.servletContext = servletContext;
this.relativeResultTypes = Set.of(relativeResultTypes.split("\\s*[,]\\s*"));
this.conventionsService = container.getInstance(ConventionsService.class, container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE));
this.relativeResultTypes = TextParseUtil.commaDelimitedStringToSet(relativeResultTypes);
String instanceName = container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE);
this.conventionsService = container.getInstance(ConventionsService.class, instanceName);
}
/**
* @param flatResultLayout If 'true' result resources will be expected to be in the form
* ${namespace}/${actionName}-${result}.${extension}, otherwise in the form
* ${namespace}/${actionName}/${result}.${extension}
* ${namespace}/${actionName}-${result}.${extension}, otherwise in the form
* ${namespace}/${actionName}/${result}.${extension}
*/
@Inject(ConventionConstants.CONVENTION_RESULT_FLAT_LAYOUT)
public void setFlatResultLayout(String flatResultLayout) {
@@ -150,8 +152,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* {@inheritDoc}
*/
public Map<String, ResultConfig> build(Class<?> actionClass,
org.apache.struts2.convention.annotation.Action annotation, String actionName,
PackageConfig packageConfig) {
org.apache.struts2.convention.annotation.Action annotation, String actionName,
PackageConfig packageConfig) {
// Get the default result location from the annotation or configuration
String defaultResultPath = conventionsService.determineResultPath(actionClass);
@@ -164,7 +166,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
// Check for resources with the action name
final String namespace = packageConfig.getNamespace();
if (namespace != null && namespace.startsWith("/")) {
defaultResultPath = defaultResultPath + namespace.substring(1);
defaultResultPath = defaultResultPath + namespace.substring(1);
} else if (namespace != null) {
defaultResultPath = defaultResultPath + namespace;
}
@@ -184,7 +186,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
Map<String, ResultConfig> results = new HashMap<>();
Map<String, ResultTypeConfig> resultsByExtension = conventionsService.getResultTypesByExtension(packageConfig);
createFromResources(actionClass, results, defaultResultPath, resultPrefix, actionName,
packageConfig, resultsByExtension);
packageConfig, resultsByExtension);
//get inherited @Results and @Result (class level)
for (Class<?> clazz : ReflectionTools.getClassHierarchy(actionClass)) {
@@ -202,11 +204,12 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
/**
* Creates results from @Results and @Result annotations
* @param actionClass class to check for annotations
* @param packageConfig packageConfig where the action will be located
* @param defaultResultPath default result path
* @param results map of results
* @param resultsByExtension map of result types keyed by extension
*
* @param actionClass class to check for annotations
* @param packageConfig packageConfig where the action will be located
* @param defaultResultPath default result path
* @param results map of results
* @param resultsByExtension map of result types keyed by extension
*/
protected void createResultsFromAnnotations(Class<?> actionClass, PackageConfig packageConfig, String defaultResultPath,
Map<String, ResultConfig> results, Map<String, ResultTypeConfig> resultsByExtension) {
@@ -228,24 +231,23 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* Creates any result types from the resources available in the web application. This scans the
* web application resources using the servlet context.
*
* @param actionClass The action class the results are being built for.
* @param results The results map to put the result configs created into.
* @param resultPath The calculated path to the resources.
* @param resultPrefix The prefix for the result. This is usually <code>/resultPath/actionName</code>.
* @param actionName The action name which is used only for logging in this implementation.
* @param packageConfig The package configuration which is passed along in order to determine
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param actionClass The action class the results are being built for.
* @param results The results map to put the result configs created into.
* @param resultPath The calculated path to the resources.
* @param resultPrefix The prefix for the result. This is usually <code>/resultPath/actionName</code>.
* @param actionName The action name which is used only for logging in this implementation.
* @param packageConfig The package configuration which is passed along in order to determine
* @param resultsByExtension The map of extensions to result type configuration instances.
*/
protected void createFromResources(Class<?> actionClass, Map<String, ResultConfig> results,
final String resultPath, final String resultPrefix, final String actionName,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) {
final String resultPath, final String resultPrefix, final String actionName,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension) {
if (LOG.isTraceEnabled()) {
LOG.trace("Searching for results in the Servlet container at [{}]" +
" with result prefix of [#1]", resultPath, resultPrefix);
LOG.trace("Searching for results in the Servlet container at [{}] with result prefix of [{}]",
resultPath, resultPrefix);
}
// Build from web application using the ServletContext
@SuppressWarnings("unchecked")
Set<String> paths = servletContext.getResourcePaths(flatResultLayout ? resultPath : resultPrefix);
if (paths != null) {
for (String path : paths) {
@@ -255,13 +257,12 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
if (StringUtils.isBlank(fileName) || StringUtils.startsWith(fileName, ".")) {
LOG.trace("Ignoring file without name [{}]", path);
continue;
}
else if(fileName.lastIndexOf(".") > 0){
String suffix = fileName.substring(fileName.lastIndexOf(".")+1);
} else if (fileName.lastIndexOf(".") > 0) {
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
if(conventionsService.getResultTypesByExtension(packageConfig).get(suffix) == null) {
if (conventionsService.getResultTypesByExtension(packageConfig).get(suffix) == null) {
LOG.debug("No result type defined for file suffix : [{}]. Ignoring file {}", suffix, fileName);
continue;
continue;
}
}
@@ -270,11 +271,10 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
// Building from the classpath
String classPathLocation = resultPath.startsWith("/") ?
resultPath.substring(1, resultPath.length()) : resultPath;
String classPathLocation = resultPath.startsWith("/") ? resultPath.substring(1) : resultPath;
if (LOG.isTraceEnabled()) {
LOG.trace("Searching for results in the class path at [{}]"
+ " with a result prefix of [{}] and action name [{}]", classPathLocation, resultPrefix,
+ " with a result prefix of [{}] and action name [{}]", classPathLocation, resultPrefix,
actionName);
}
@@ -296,7 +296,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
}
} catch (IOException ex) {
LOG.error("Unable to scan directory [{}] for results", ex, classPathLocation);
LOG.error("Unable to scan directory [{}] for results", classPathLocation, ex);
}
}
@@ -320,30 +320,28 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
private Test<URL> getResourceTest(final String resultPath, final String actionName) {
return new Test<URL>() {
public boolean test(URL url) {
String urlStr = url.toString();
int index = urlStr.lastIndexOf(resultPath);
String path = urlStr.substring(index + resultPath.length());
return path.startsWith(actionName);
}
return url -> {
String urlStr = url.toString();
int index = urlStr.lastIndexOf(resultPath);
String path = urlStr.substring(index + resultPath.length());
return path.startsWith(actionName);
};
}
/**
* Makes all the results for the given path.
*
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param resultPrefix The is the result prefix which is the result location plus the action name.
* This is used to determine if the path contains a result code or not.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param resultPrefix The is the result prefix which is the result location plus the action name.
* This is used to determine if the path contains a result code or not.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
*/
protected void makeResults(Class<?> actionClass, String path, String resultPrefix,
Map<String, ResultConfig> results, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultConfig> results, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
if (path.startsWith(resultPrefix)) {
int indexOfDot = path.indexOf('.', resultPrefix.length());
@@ -351,26 +349,27 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
// This case is when the path doesn't contain a result code
if (indexOfDot == resultPrefix.length()) {
if (LOG.isTraceEnabled()) {
LOG.trace("The result file [{}] has no result code and therefore" +
" will be associated with success, input and error by default. This might" +
" be overridden by another result file or an annotation.", path);
LOG.trace("""
The result file [{}] has no result code and therefore
will be associated with success, input and error by default.
This might be overridden by another result file or an annotation.
""".stripIndent(), path);
}
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.SUCCESS);
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.INPUT);
addResult(actionClass, path, results, packageConfig, resultsByExtension, Action.ERROR);
// This case is when the path contains a result code
// This case is when the path contains a result code
} else if (indexOfDot > resultPrefix.length()) {
if (LOG.isTraceEnabled()) {
LOG.trace("The result file [{}] has a result code and therefore" +
" will be associated with only that result code.", path);
LOG.trace("The result file [{}] has a result code and therefore will be associated with only that result code.", path);
}
String resultCode = path.substring(resultPrefix.length() + 1, indexOfDot);
ResultConfig result = createResultConfig(actionClass,
new ResultInfo(resultCode, path, packageConfig, resultsByExtension),
packageConfig, null);
new ResultInfo(resultCode, path, packageConfig, resultsByExtension),
packageConfig, null);
results.put(resultCode, result);
}
}
@@ -380,16 +379,16 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* Checks if result was already assigned, if not checks global results first and if exists, adds reference to it.
* If not, creates package specific result.
*
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param resultKey The result name to use
* @param actionClass The action class the results are being built for.
* @param path The path to build the result for.
* @param results The Map to place the result(s)
* @param packageConfig The package config the results belong to.
* @param resultsByExtension The map of extensions to result type configuration instances.
* @param resultKey The result name to use
*/
protected void addResult(Class<?> actionClass, String path, Map<String, ResultConfig> results,
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension,
String resultKey) {
PackageConfig packageConfig, Map<String, ResultTypeConfig> resultsByExtension,
String resultKey) {
if (!results.containsKey(resultKey)) {
Map<String, ResultConfig> globalResults = packageConfig.getAllGlobalResults();
@@ -405,8 +404,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
protected void createFromAnnotations(Map<String, ResultConfig> resultConfigs,
String resultPath, PackageConfig packageConfig, Result[] results,
Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) {
String resultPath, PackageConfig packageConfig, Result[] results,
Class<?> actionClass, Map<String, ResultTypeConfig> resultsByExtension) {
// Check for multiple results on the class
for (Result result : results) {
for (String name : result.name()) {
@@ -425,25 +424,23 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
* information from the annotation and anything that isn't specified will be fetched from the
* PackageConfig defaults (if they exist).
*
* @param actionClass The action class the results are being built for.
* @param info The result info that is used to create the ResultConfig instance.
* @param packageConfig The PackageConfig to use to fetch defaults for result and parameters.
* @param result (Optional) The result annotation to pull additional information from.
* @return The ResultConfig or null if the Result annotation is given and the annotation is
* targeted to some other action than this one.
* @param actionClass The action class the results are being built for.
* @param info The result info that is used to create the ResultConfig instance.
* @param packageConfig The PackageConfig to use to fetch defaults for result and parameters.
* @param result (Optional) The result annotation to pull additional information from.
* @return The ResultConfig or null if the Result annotation is given and the annotation is
* targeted to some other action than this one.
*/
@SuppressWarnings(value = {"unchecked"})
protected ResultConfig createResultConfig(Class<?> actionClass, ResultInfo info,
PackageConfig packageConfig, Result result) {
protected ResultConfig createResultConfig(Class<?> actionClass, ResultInfo info, PackageConfig packageConfig, Result result) {
// Look up by the type that was determined from the annotation or by the extension in the
// ResultInfo class
ResultTypeConfig resultTypeConfig = packageConfig.getAllResultTypeConfigs().get(info.type);
if (resultTypeConfig == null) {
throw new ConfigurationException("The Result type [" + info.type + "] which is" +
" defined in the Result annotation on the class [" + actionClass + "] or determined" +
" by the file extension or is the default result type for the PackageConfig of the" +
" action, could not be found as a result-type defined for the Struts/XWork package [" +
packageConfig.getName() + "]");
" defined in the Result annotation on the class [" + actionClass + "] or determined" +
" by the file extension or is the default result type for the PackageConfig of the" +
" action, could not be found as a result-type defined for the Struts/XWork package [" +
packageConfig.getName() + "]");
}
// Add the default parameters for the result type config (if any)
@@ -474,15 +471,15 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
public final String type;
public ResultInfo(String name, String location, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultTypeConfig> resultsByExtension) {
this.name = name;
this.location = location;
this.type = determineType(location, packageConfig, resultsByExtension);
}
public ResultInfo(String name, Result result, PackageConfig packageConfig,
String resultPath, Class<?> actionClass,
Map<String, ResultTypeConfig> resultsByExtension) {
String resultPath, Class<?> actionClass,
Map<String, ResultTypeConfig> resultsByExtension) {
this.name = name;
if (StringUtils.isNotBlank(result.type())) {
this.type = result.type();
@@ -490,8 +487,8 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
this.type = determineType(result.location(), packageConfig, resultsByExtension);
} else {
throw new ConfigurationException("The action class [" + actionClass + "] contains a " +
"result annotation that has no type parameter and no location parameter. One of " +
"these must be defined.");
"result annotation that has no type parameter and no location parameter. One of " +
"these must be defined.");
}
// See if we can handle relative locations or not
@@ -507,7 +504,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
}
String determineType(String location, PackageConfig packageConfig,
Map<String, ResultTypeConfig> resultsByExtension) {
Map<String, ResultTypeConfig> resultsByExtension) {
int indexOfDot = location.lastIndexOf(".");
if (indexOfDot > 0) {
String extension = location.substring(indexOfDot + 1);
@@ -516,7 +513,7 @@ public class DefaultResultMapBuilder implements ResultMapBuilder {
return resultTypeConfig.getName();
} else
throw new ConfigurationException("Unable to find a result type for extension [" + extension + "] " +
"in location attribute [" + location + "].");
"in location attribute [" + location + "].");
} else {
return packageConfig.getFullDefaultResultType();
}
@@ -18,36 +18,48 @@
*/
package org.apache.struts2.convention;
import jakarta.servlet.ServletContext;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.config.entities.ResultConfig;
import org.apache.struts2.config.entities.ResultTypeConfig;
import org.apache.struts2.inject.Container;
import junit.framework.TestCase;
import org.apache.struts2.convention.actions.NoAnnotationAction;
import org.apache.struts2.convention.actions.result.*;
import org.apache.struts2.convention.actions.result.ActionLevelResultAction;
import org.apache.struts2.convention.actions.result.ActionLevelResultsAction;
import org.apache.struts2.convention.actions.result.ActionLevelResultsNamesAction;
import org.apache.struts2.convention.actions.result.ClassLevelResultAction;
import org.apache.struts2.convention.actions.result.ClassLevelResultsAction;
import org.apache.struts2.convention.actions.result.GlobalResultAction;
import org.apache.struts2.convention.actions.result.GlobalResultOverrideAction;
import org.apache.struts2.convention.actions.result.InheritedResultExtends;
import org.apache.struts2.convention.actions.result.InheritedResultsExtends;
import org.apache.struts2.convention.actions.result.OverrideInheritedResultExtends;
import org.apache.struts2.convention.actions.result.OverrideResultAction;
import org.apache.struts2.convention.actions.resultpath.ClassLevelResultPathAction;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.inject.Container;
import org.apache.struts2.result.ServletDispatcherResult;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Test;
import jakarta.servlet.ServletContext;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.apache.struts2.convention.ReflectionTools.getAnnotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* <p>
* This class tests the simple result map builder.
* </p>
*/
public class DefaultResultMapBuilderTest extends TestCase {
public class DefaultResultMapBuilderTest {
private Container container;
private ConventionsService conventionsService;
public void testBuild() throws Exception {
@Test
public void testBuild() {
ServletContext context = mockServletContext("/WEB-INF/location");
// Test with a slash
@@ -66,7 +78,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
verify(context, "/WEB-INF/location", results, false);
}
public void testResultOverrride() throws Exception {
@Test
public void testResultOverride() throws Exception {
ServletContext context = mockServletContext("/WEB-INF/location");
// Test with a slash
@@ -80,7 +93,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals("/WEB-INF/location/namespace/error-overriden.jsp", result.getParams().get("location"));
}
public void testGlobalResult() throws Exception {
@Test
public void testGlobalResult() {
ServletContext context = mockServletContext("/WEB-INF/location");
this.conventionsService = new ConventionsServiceImpl("/WEB-INF/location");
@@ -105,8 +119,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals("/globalError.jsp", result.getParams().get("location"));
}
public void testGlobalResultOverride() throws Exception {
@Test
public void testGlobalResultOverride() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
String resultPath = "/WEB-INF/location";
// Setup some mock jsps
@@ -139,7 +153,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
assertEquals(resultPath + "/namespace/action-error.jsp", result.getParams().get("location"));
}
public void testNull() throws Exception {
@Test
public void testNull() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
EasyMock.expect(context.getResourcePaths("/WEB-INF/location/namespace/")).andReturn(null);
EasyMock.replay(context);
@@ -153,7 +168,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testResultPath() throws Exception {
@Test
public void testResultPath() {
ServletContext context = mockServletContext("/class-level");
// Test with a result path
@@ -164,7 +180,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
verify(context, "/class-level", results, false);
}
public void testFromServletContextWithBadNames() throws Exception {
@Test
public void testFromServletContextWithBadNames() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -184,7 +201,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testFromServletContext() throws Exception {
@Test
public void testFromServletContext() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -218,7 +236,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testFromServletContextNotFlat() throws Exception {
@Test
public void testFromServletContextNotFlat() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -254,7 +273,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testIgnoreFilesWithoutName() throws Exception {
@Test
public void testIgnoreFilesWithoutName() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -278,7 +298,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
}
public void testClassLevelSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -301,7 +322,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelInheritedSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelInheritedSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -324,7 +346,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelOverwriteInheritedSingleResultAnnotation() throws Exception {
@Test
public void testClassLevelOverwriteInheritedSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -349,7 +372,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelMultipleResultAnnotation() throws Exception {
@Test
public void testClassLevelMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -388,7 +412,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassLevelInheritanceMultipleResultAnnotation() throws Exception {
@Test
public void testClassLevelInheritanceMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -427,7 +452,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelSingleResultAnnotation() throws Exception {
@Test
public void testActionLevelSingleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -450,7 +476,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultAnnotation() throws Exception {
@Test
public void testActionLevelMultipleResultAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -485,7 +512,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultNamesAnnotation() throws Exception {
@Test
public void testActionLevelMultipleResultNamesAnnotation() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -520,7 +548,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testActionLevelMultipleResultNamesAnnotationNoName() throws Exception {
@Test
public void testActionLevelMultipleResultNamesAnnotationNoName() {
ServletContext context = EasyMock.createStrictMock(ServletContext.class);
// Setup some mock jsps
@@ -543,7 +572,8 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
public void testClassPath() throws Exception {
@Test
public void testClassPath() {
ServletContext context = EasyMock.createNiceMock(ServletContext.class);
ResultTypeConfig resultType = new ResultTypeConfig.Builder("freemarker", "org.apache.struts2.result.ServletDispatcherResult").
@@ -600,8 +630,7 @@ public class DefaultResultMapBuilderTest extends TestCase {
return context;
}
private void verify(ServletContext context, String resultPath, Map<String, ResultConfig> results,
boolean redirect) {
private void verify(ServletContext context, String resultPath, Map<String, ResultConfig> results, boolean redirect) {
assertEquals(4, results.size());
assertEquals("success", results.get("success").getName());
assertEquals("input", results.get("input").getName());
@@ -633,16 +662,13 @@ public class DefaultResultMapBuilderTest extends TestCase {
EasyMock.verify(context);
}
@Override
protected void setUp() throws Exception {
super.setUp();
@Before
public void setup() {
this.container = EasyMock.createNiceMock(Container.class);
EasyMock.expect(container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE)).andReturn("convention").anyTimes();
EasyMock.expect(container.getInstance(ConventionsService.class, "convention")).andAnswer(new IAnswer<ConventionsService>() {
public ConventionsService answer() throws Throwable {
return DefaultResultMapBuilderTest.this.conventionsService;
}
}).anyTimes();
EasyMock.expect(container.getInstance(String.class, ConventionConstants.CONVENTION_CONVENTIONS_SERVICE))
.andReturn("convention").anyTimes();
EasyMock.expect(container.getInstance(ConventionsService.class, "convention"))
.andAnswer(() -> DefaultResultMapBuilderTest.this.conventionsService).anyTimes();
EasyMock.replay(this.container);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jasperreports-plugin</artifactId>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-javatemplates-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-jfreechart-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-json-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-junit-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-plugins</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-rest-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-spring-plugin</artifactId>
@@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
@@ -87,4 +88,91 @@ public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectProxyMember, ""));
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
}
@Test
public void nullTargetAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullTargetAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullTargetAndTargetAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertTrue(sma.isAccessible(context, null, proxyObjectProxyMember, ""));
}
@Test
public void nullMemberAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullMemberAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullMemberAndTargetNotAllowedAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
Object action = proxy.getAction();
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, action, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetNotAllowedAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAllowedAndMemberNotAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullTargetAndMemberAndTargetAndMemberAllowed() {
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
assertThrows("Member cannot be null!", IllegalArgumentException.class,
() -> sma.isAccessible(context, null, null, ""));
}
@Test
public void nullPropertyName() {
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
Object action = proxy.getAction();
assertTrue(sma.isAccessible(context, action, proxyObjectProxyMember, null));
}
}
@@ -23,9 +23,9 @@ import org.apache.struts2.SimpleAction;
import org.apache.struts2.TestBean;
import org.apache.struts2.TestSubBean;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.config.providers.XmlConfigurationProvider;
import org.apache.struts2.util.ProxyUtil;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.springframework.context.ApplicationContext;
/**
@@ -46,6 +46,8 @@ public class SpringProxyUtilTest extends XWorkTestCase {
}
public void testIsProxy() throws Exception {
assertFalse(ProxyUtil.isProxy(null));
Object simpleAction = appContext.getBean("simple-action");
assertFalse(ProxyUtil.isProxy(simpleAction));
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-testng-plugin</artifactId>
+2 -2
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-tiles-plugin</artifactId>
@@ -40,7 +40,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.4.1</version>
<version>3.5.0</version>
<executions>
<execution>
<phase>compile</phase>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-velocity-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
</parent>
<artifactId>struts2-xslt-plugin</artifactId>
+11 -113
View File
@@ -29,7 +29,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>struts2-parent</artifactId>
<version>7.0.0</version>
<version>7.0.3</version>
<packaging>pom</packaging>
<name>Struts 2</name>
<url>https://struts.apache.org/</url>
@@ -51,7 +51,7 @@
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
<tag>STRUTS_7_0_0</tag>
<tag>STRUTS_7_0_3</tag>
</scm>
<issueManagement>
@@ -104,18 +104,18 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2024-12-11T06:54:21Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2025-02-17T09:39:05Z</project.build.outputTimestamp>
<maven.compiler.release>17</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<!-- dependency versions in alphanumeric order -->
<asm.version>9.7.1</asm.version>
<byte-buddy.version>1.14.11</byte-buddy.version>
<freemarker.version>2.3.33</freemarker.version>
<byte-buddy.version>1.16.1</byte-buddy.version>
<freemarker.version>2.3.34</freemarker.version>
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<jackson.version>2.18.2</jackson.version>
<log4j2.version>2.24.2</log4j2.version>
<log4j2.version>2.24.3</log4j2.version>
<maven-surefire-plugin.version>3.5.2</maven-surefire-plugin.version>
<mockito.version>5.8.0</mockito.version>
<ognl.version>3.3.5</ognl.version>
@@ -484,7 +484,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.17.1</version>
<version>2.18.0</version>
<reportSets>
<reportSet>
<reports>
@@ -606,42 +606,11 @@
<version>3.1.8</version>
</dependency>
<!-- override version of error_prone_annotations until caffeine updates from 2.10.0 to 2.23.0 -->
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<version>2.23.0</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>6.0.3</version>
<scope>provided
</scope> <!-- felix-main provides everything in felix-framework (and more), override here to provent both JARs from being included in the build. -->
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.main</artifactId>
<version>7.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell</artifactId>
<version>1.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.shell.tui</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Velocity -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
<version>2.4.1</version>
</dependency>
<dependency>
@@ -727,40 +696,12 @@
<version>5.0.0-M1</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jakarta.servlet.jsp.jstl</artifactId>
<version>3.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>10.1.15</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-api</artifactId>
<version>10.1.15</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
@@ -768,19 +709,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>request</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
<version>10.1.15</version>
</dependency>
<!-- Commons -->
<dependency>
<groupId>commons-logging</groupId>
@@ -800,7 +728,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
<version>2.18.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -810,12 +738,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>commons-el</groupId>
<artifactId>commons-el</artifactId>
<version>1.0</version>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -884,7 +807,7 @@
<dependency>
<groupId>org.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>4.2.0</version>
<version>4.9.0</version>
<scope>test</scope>
</dependency>
@@ -921,13 +844,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>jmock</groupId>
<artifactId>jmock-cglib</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@@ -983,17 +899,6 @@
</exclusions>
</dependency>
<dependency>
<groupId>io.github.x-stream</groupId>
<artifactId>mxparser</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
@@ -1041,13 +946,6 @@
<version>5.1.2.Final</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.12.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>