mirror of
https://github.com/apache/struts.git
synced 2026-08-07 15:46:57 +00:00
Compare commits
65 Commits
STRUTS_7_2_1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b1a7c22969 | |||
| 0fd3d7a2fb | |||
| 702280f48f | |||
| 687436f9bf | |||
| a6570b769b | |||
| 1218c49224 | |||
| a07d0e2926 | |||
| 81264063e6 | |||
| 26141511f8 | |||
| d52ffed7ff | |||
| 94a8fcb26c | |||
| 6f802987f5 | |||
| d906f23448 | |||
| 532ca7f864 | |||
| 0b2bc2be14 | |||
| 12015d0bf5 | |||
| 833220346c | |||
| c3e9db1b21 | |||
| 8a5323fbdf | |||
| 73f17c1be7 | |||
| eb69ad6f3a | |||
| 88d95b2a5f | |||
| ef8361caa7 | |||
| a324538650 | |||
| b154b7ca43 | |||
| 9f030f62be | |||
| f32270cdb0 | |||
| f5880be2ef | |||
| 6d27132926 | |||
| f2c1f50da2 | |||
| 464817e0b0 | |||
| b2548542ec | |||
| 11c10ee8f9 | |||
| 60bc7b8d7a | |||
| 963a81f43d | |||
| cc00343f1b | |||
| db64c103d8 | |||
| 31036a8a49 | |||
| 6cb6abf4ba | |||
| de599c91d3 | |||
| e5eb01abda | |||
| cf22320e33 | |||
| 40fcae3101 | |||
| b70ecc8e15 | |||
| 18955b98a4 | |||
| 75a285a106 | |||
| 8fb8bcbcf0 | |||
| f03198c6f8 | |||
| 525c7dae37 | |||
| 5c130f9411 | |||
| 08fba59807 | |||
| 30cbd1c3c4 | |||
| 16aa43137d | |||
| 745c08c7ac | |||
| 789dbf3cd2 | |||
| a09f2a93af | |||
| 29f75f2dc0 | |||
| dc90e48909 | |||
| 50c16e93b1 | |||
| 07afccbb2f | |||
| 153e42bb9b | |||
| 46ba90f797 | |||
| 22243e75f5 | |||
| 1e3fa9a2d7 | |||
| 01a561d9d8 |
@@ -0,0 +1,213 @@
|
||||
---
|
||||
name: creating-security-bulletins
|
||||
description: Use when drafting, updating, or reviewing an S2-XXX security bulletin on the Struts cwiki, when preparing bulletin text ahead of a CVE request, or when deciding how much detail about a fixed vulnerability is safe to publish.
|
||||
---
|
||||
|
||||
# Creating Security Bulletins
|
||||
|
||||
## Overview
|
||||
|
||||
An S2-XXX bulletin exists to tell an operator **what to upgrade and why** — not to explain the defect. Every sentence that helps a defender must be weighed against how much it helps someone building an exploit.
|
||||
|
||||
**Core principle:** every field is either traced to source you read this session, or a visible placeholder. Never a plausible guess.
|
||||
|
||||
**Process authority:** [`SECURITY.md`](../../../SECURITY.md) governs disclosure. This skill governs *what the page says and how it is written*.
|
||||
|
||||
**REQUIRED BACKGROUND:** the claims you put in a bulletin come from triage. Use `triaging-security-reports` to establish them before writing.
|
||||
|
||||
## The Iron Rule
|
||||
|
||||
```
|
||||
NO FIELD IN A BULLETIN WITHOUT A SOURCE YOU READ THIS SESSION,
|
||||
OR A VISIBLE PLACEHOLDER.
|
||||
```
|
||||
|
||||
Applies to the severity rating, the affected versions, and above all the Workaround. "There is no workaround" is a factual claim about absence — the hardest kind to get right, and the most common thing to assert without checking.
|
||||
|
||||
## Page structure
|
||||
|
||||
Sections in order, matching the existing published bulletins:
|
||||
|
||||
`Summary` (in an `excerpt` macro) → field table → `Problem` → `Solution` → `Backward compatibility` → `Workaround`
|
||||
|
||||
Field table rows, in order:
|
||||
|
||||
| Row | Content |
|
||||
|---|---|
|
||||
| Who should read this | Usually `All Struts 2 developers and users`; narrow it only when exposure is genuinely conditional |
|
||||
| Impact of vulnerability | A short impact phrase, not a paragraph — *Remote Code Execution*, *Denial of service*, *Disclosure of Data, Denial of Service, Server Side Request Forgery*. Hedging is accepted where warranted (*Possible Remote Code Execution vulnerability*) |
|
||||
| Maximum security rating | Low / Moderate / Important / Critical — see the rating scale below |
|
||||
| Recommendation | `Upgrade to Struts X.Y.Z at least`. Name **every** maintenance line that carries the fix (`Upgrade to Struts 6.8.0 or 7.1.1 at least`), and add the required action where upgrading alone is not enough (`… and use Action File Upload Interceptor`) |
|
||||
| Affected Software | Officially released versions only (see below); bullet one range per maintenance line, linking the EOL announcement for end-of-life ranges |
|
||||
| Reporters | Credit the reporter — they earned it, and it costs nothing. Include their organisation where they gave one (`Steven Seeley of Source Incite`); obfuscate any email (`pwntester at github dot com`) |
|
||||
| CVE Identifier | Placeholder until assigned (see below) |
|
||||
|
||||
**Match the house voice — from the *recent* bulletins only.** Read the two or three most recently published ones before writing. They are far terser than a triage write-up: `Problem` is one to three sentences, and every affected feature is **linked to its page on struts.apache.org** so an operator can go straight to the documentation. Where a bulletin resembles an earlier one, the Summary says so and links it.
|
||||
|
||||
**Do not take the older bulletins as a precedent for how much to disclose.** Earlier advisories explained causes and mitigations in far more detail, and that detail was used to build working exploits. The project deliberately stopped. An old bulletin naming the exact construct that triggers the flaw is evidence of the practice this skill exists to prevent, not licence to repeat it — mine them for structure and tone, never for depth.
|
||||
|
||||
## Affected Software: released versions only
|
||||
|
||||
**List only versions that passed a PMC release vote.** A build that was cut, failed its test period, and was superseded never reached users as a release — listing it implies an official artifact was vulnerable and drags a phantom version into every downstream CVE record and scanner database.
|
||||
|
||||
Verify before writing. Do not infer the range from the tags in git: a tag exists for builds that were never voted through. Ask, or check the release announcements.
|
||||
|
||||
**Deriving the lower bound** — one method, both bounds:
|
||||
|
||||
1. Find when the vulnerable code entered, with `git log -S'<the vulnerable construct>' -- <path>`. Do not assume it arrived with the feature that made it reachable; a defect often predates the control that was supposed to bound it.
|
||||
2. Map that commit to the first *release* containing it.
|
||||
3. If step 2 can't be settled from what you have, write a visible placeholder naming what must be confirmed — never a guessed version number.
|
||||
|
||||
## The rating scale is published — apply it, don't invent one
|
||||
|
||||
The definitions live on **[Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758)** (page `61758`), and they answer one question: *how worried should I be about this vulnerability?*
|
||||
|
||||
**That page is the only authority.** The four-level naming was introduced comparatively recently, so bulletins published before it use other wording and inconsistent capitalisation. Never infer the vocabulary or calibrate a rating from an older bulletin — match a definition on page `61758`, and take comparisons only from advisories published since the scale existed.
|
||||
|
||||
| Rating | Applies when |
|
||||
|---|---|
|
||||
| **Critical** | A remote attacker can get Struts to execute arbitrary code — exploitable automatically, regardless of whether the developer followed the Security Guide |
|
||||
| **Important** | Compromise of the application's **data or availability**; also easy RCE that depends on the developer having mistreated user input |
|
||||
| **Moderate** | There is **significant mitigation**: the flaw does not affect likely configurations, or the configuration is not widely used, or the attacker must be authenticated |
|
||||
| **Low** | Everything else — believed **extremely hard to exploit**, or the exploit yields minimal consequences |
|
||||
|
||||
Two traps in applying it:
|
||||
|
||||
- **Low is not "narrow".** A flaw that is trivial to trigger and causes real damage is not Low merely because a setting gates it. Reserve Low for hard-to-exploit *or* minimal-consequence.
|
||||
- **The Moderate clause is "not widely used", not "opt-in".** A gate only mitigates if few deployments pass through it. S2-068 needed file upload enabled and was still rated **Important**, because file upload is ordinary. Ask how many real deployments the precondition actually excludes.
|
||||
- **Availability counts as Important.** Denial of service is not automatically a lesser class — S2-068 was disk exhaustion, rated Important. It drops to Moderate only where a mitigation clause genuinely applies.
|
||||
|
||||
**Exploitation status belongs on the page, not in the rating.** The scale measures the flaw itself, so it has no slot for "a public reproduction already exists." When a defect was disclosed publicly before the fix shipped, or a working reproduction is already public, say so in plain words — downstream consumers are told by their own regulators to prioritise on real risk and active exploitation, not on a severity class alone. It costs nothing: the reproduction is already out.
|
||||
|
||||
## CVE placeholder
|
||||
|
||||
CVEs are requested **after** the fixed release is out and accepted. Until then the row carries a placeholder that cannot be mistaken for a real identifier:
|
||||
|
||||
```
|
||||
CVE-YYYY-NNNNN (to be assigned before publication)
|
||||
```
|
||||
|
||||
Never leave a cloned page's real CVE in place. Never invent a well-formed-looking number.
|
||||
|
||||
One CVE per independently fixable issue — separate fixes get separate bulletins and separate CVEs, per [CNA rules 4.1.10](https://www.cve.org/ResourcesSupport/AllResources/CNARules).
|
||||
|
||||
## The disclosure budget
|
||||
|
||||
**The budget covers every prose section — `Problem`, `Backward compatibility`, and `Workaround` alike.** `Problem` is the section authors guard; `Backward compatibility` is the one that leaks, because describing what changed about the fixed behaviour describes the defect. A note saying which inputs are handled differently now points straight at the code path that was rewritten. Apply the table below to all three sections, and write the BC note in terms of what an application might *observe*, never what the fix altered internally.
|
||||
|
||||
Write the shortest true description that lets an operator judge whether they are exposed. One to three sentences, as in the published bulletins.
|
||||
|
||||
| Safe to publish | Never publish before the fix is out |
|
||||
|---|---|
|
||||
| Impact categories and consequence | Class, method, or field names |
|
||||
| The component in plain words, linked to its documentation | `file:line` references |
|
||||
| Whether a configured control fails to apply | Commit hashes, PR or Jira numbers |
|
||||
| That state is shared / input is unvalidated | The triggering request shape or payload |
|
||||
| Which released versions are affected | Reproduction steps, PoC, timing conditions |
|
||||
|
||||
**Write for an operator, not a reviewer.** S2-068 describes an exploited disk-exhaustion bug in one sentence — *"If support for file upload is enabled, file leak in multipart request processing causes disk exhaustion."* That is the register: the feature, the failure, the consequence. Naming the class turns a bulletin into a starting point.
|
||||
|
||||
## State who is *not* affected
|
||||
|
||||
An operator's first question is "does this reach me?" Answer it on the page, or every reader has to assume it does.
|
||||
|
||||
The house form is **one sentence, linked to the feature's documentation** — S2-067 does it in a single line:
|
||||
|
||||
> **Note**: applications not using [FileUploadInterceptor](https://struts.apache.org/core-developers/file-upload-interceptor) are safe.
|
||||
|
||||
or folded into the opening clause, as S2-068 does with *"If support for file upload is enabled, …"*. Say it whenever exposure is conditional — an optional plugin the application chooses to ship, a setting that must be switched on, an endpoint that must be mapped, or an unaffected sibling path that lets a reader stop reading. Add "earlier releases are not affected" when there is a clean prior baseline.
|
||||
|
||||
Keep it at the level of a deployment decision ("uses the plugin", "exposes such an endpoint"), not a code path. Scoping *reduces* net disclosure: it shrinks the population that has to care, and it costs an attacker nothing they could not learn from the dependency list.
|
||||
|
||||
## Fix provenance
|
||||
|
||||
A bulletin promises a fixed release and describes post-fix behaviour as settled fact. Both claims rest on a specific change.
|
||||
|
||||
**Record which commit or PR each behavioural claim rests on**, in the version comment or your notes — not on the page.
|
||||
|
||||
**Confirm that change is merged into the release branch before publishing.** A patch under private review may be revised or dropped; a bulletin describing behaviour that never shipped is worse than a late bulletin. Bulletins are routinely drafted while the fix is still embargoed and unmerged — that is normal, and it is exactly why the merge state must be re-checked at publication time rather than at drafting time.
|
||||
|
||||
**Derive BC notes from the fix diff, not from its commit message.** A commit summary that calls the behaviour unchanged can still carry an observable difference its author did not think worth mentioning. Read the diff.
|
||||
|
||||
**`Backward compatibility` is also where a breaking upgrade is announced**, and the announcement has to be blunt. S2-067 told users the fix was *not* backward compatible, that they had to rewrite their actions onto a new mechanism, and that staying on the old one left them vulnerable. Where the fix is transparent, the house sentence is simply *"This change is backward compatible."*
|
||||
|
||||
## Workaround: verify or say nothing
|
||||
|
||||
Three valid outcomes, in order of preference:
|
||||
|
||||
1. **A verified configuration or operational change.** Trace it in source and confirm it actually removes reachability. Give the change, not the mechanism. It need not be a Struts setting — S2-068 offers a sized or dedicated temp volume, and pointing at the relevant section of the Security Guide is a legitimate workaround in itself.
|
||||
2. **Upgrade only** — when you checked and found nothing.
|
||||
3. **Verified absence.** The house value is a bare `n/a` (S2-066, S2-067); spell it out when the reason is worth stating.
|
||||
|
||||
Never ship a workaround you reasoned about but did not confirm. A wrong workaround leaves operators believing they are protected and discredits every other field on the page.
|
||||
|
||||
**The tension to decide deliberately:** a workaround usually reveals which path is affected. That is often the right trade — it is why the bulletin exists — but it is a decision to make and surface, not one to make silently. Say which way you went and why.
|
||||
|
||||
## Re-read the page immediately before you write to it
|
||||
|
||||
Bulletins are drafted by more than one person, often within the same hour. Content you read earlier may have moved on — a backport range added, a placeholder resolved, a section rewritten.
|
||||
|
||||
**Fetch the current version immediately before every write, and compare the returned version number against the one you read.** If it advanced, re-read, merge your change onto the newer content, and write that. Writing from a stale copy silently discards someone else's work with no warning and no conflict error.
|
||||
|
||||
After writing, diff your new version against the one you meant to build on. The diff should show only your intended change. If it shows deletions you did not intend, restore from history and redo the edit on top.
|
||||
|
||||
## Restrictions
|
||||
|
||||
Bulletins stay restricted until the coordinated publication date.
|
||||
|
||||
**Check restrictions before the edit and again after.** An API update should not disturb them, but "should not" is not verification, and an accidentally public pre-release bulletin is an unrecoverable disclosure.
|
||||
|
||||
Expected on the Struts wiki: read and update limited to the author plus `struts-committers`.
|
||||
|
||||
## Start from the template, never from a previous bulletin
|
||||
|
||||
**[`bulletin-template.md`](bulletin-template.md)** — the field reference, per-section guidance, pre-publication checklist, and a storage-format skeleton ready to POST to the Confluence API. **It is the source of truth.**
|
||||
|
||||
A rendered copy exists on the wiki as a restricted child of *Security Bulletins* for authors who prefer to copy a page; when the two disagree, fix the wiki page from the file. Whichever route you take, confirm the new page carries the same restrictions before typing anything into it, and give the `excerpt` macro a fresh `ac:macro-id` — a copied page inherits the template's, and two bulletins must not share one.
|
||||
|
||||
**If you inherit a page cloned from a previous bulletin instead**, assume every field is inherited and wrong until you have replaced it. The residue that survives a careless edit:
|
||||
|
||||
- The previous bulletin's real CVE identifier
|
||||
- Its affected versions, rating, and reporter credit
|
||||
- Its workaround — describing a mitigation for an unrelated defect
|
||||
- The `excerpt` macro's `ac:macro-id`, now **duplicated across two pages** — generate a fresh UUID
|
||||
|
||||
Read the whole page and rewrite it; do not patch the fields you happen to notice.
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- About to write a Workaround you have not traced in source
|
||||
- About to write "no workaround exists" without having looked
|
||||
- Affected Software copied from a git tag list rather than confirmed releases
|
||||
- A CVE number on the page that you did not receive from the CVE assignment process
|
||||
- Naming a class, method, or file in `Problem` "because it's already public in the PR"
|
||||
- Copying the disclosure depth of an older bulletin — that depth is the reason this budget exists
|
||||
- Calibrating a rating against a bulletin published before the four-level scale existed
|
||||
- Guarding `Problem` carefully and then describing the fix's internals in `Backward compatibility`
|
||||
- Writing a BC note from the fix's commit message without reading the diff
|
||||
- Publishing while the fix is still unmerged, or without re-checking that it landed
|
||||
- No statement of who is *not* affected, when exposure depends on a plugin or an opt-in setting
|
||||
- Writing a page from content you read earlier in the session without re-fetching it first
|
||||
- Publishing without re-checking restrictions
|
||||
- A severity rating chosen by feel, or by reachability alone, without checking it against the published scale
|
||||
- Rating something Low because the feature is opt-in — opt-in is the definition of Moderate
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Reality |
|
||||
|---|---|
|
||||
| "The PR is public, so detail costs nothing" | A bulletin is indexed, permanent, and read by people who never see the PR. Aggregation is the harm. |
|
||||
| "An older bulletin explained the cause in detail" | Those explanations were used to build exploits. The practice was stopped deliberately — don't restore it. |
|
||||
| "An older bulletin rated something like this X" | The rating scale postdates it. Match a definition on page 61758 instead. |
|
||||
| "Listing the failed build is more honest" | It is less accurate. That build was never a release; listing it misdirects every downstream consumer. |
|
||||
| "Disabling the feature is an obvious workaround" | Obvious ≠ verified. Confirm the feature is genuinely on the only reachable path. |
|
||||
| "The rating is roughly right" | Ratings drive upgrade urgency. Read the published definitions and match one, don't approximate. |
|
||||
| "It needs an opt-in feature, so it's Low" | That is the Moderate mitigation clause. Low means hard to exploit or minimal consequence. |
|
||||
| "Restrictions were set when the page was created" | Verify after every edit. The cost of being wrong once is total. |
|
||||
| "I'll fill in the CVE later" | Only if the placeholder is unmistakable. A blank or a stale number ships as fact. |
|
||||
| "The BC note is just a compatibility courtesy" | It describes what the fix changed, which describes the defect. Same budget as `Problem`. |
|
||||
| "The commit message says behaviour is unchanged" | Commit summaries understate. Read the diff and decide for yourself. |
|
||||
| "Naming the plugin narrows it for an attacker too" | They can read your dependency list. Scoping spares every operator who isn't exposed. |
|
||||
| "The patch is reviewed, so the release will contain it" | Reviewed is not merged. Re-check at publication, not at drafting. |
|
||||
| "Copying the last bulletin is quicker than the template" | It is how another advisory's CVE ships on your page. Copy the template. |
|
||||
| "I read the page a few minutes ago" | Someone else may have written to it since. Re-fetch, then write. There is no conflict warning. |
|
||||
@@ -0,0 +1,152 @@
|
||||
# Security Bulletin Template
|
||||
|
||||
The canonical skeleton and per-field guidance for an S2-XXX security bulletin.
|
||||
Companion to [`SKILL.md`](SKILL.md), which covers *how* to establish the facts that
|
||||
go in these fields; this file covers *what the page contains*.
|
||||
|
||||
A rendered copy lives on the Struts wiki as a restricted child of
|
||||
[Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758)
|
||||
for authors who prefer to copy a page. **This file is the source of truth** — when the
|
||||
two disagree, fix the wiki page from here.
|
||||
|
||||
**Draft bulletins stay restricted** (read and update limited to the author plus
|
||||
`struts-committers`) until the coordinated publication date. Check restrictions before
|
||||
an edit and again after it: an accidentally public pre-release bulletin is an
|
||||
unrecoverable disclosure.
|
||||
|
||||
## Fields
|
||||
|
||||
| Row | What goes in it |
|
||||
|---|---|
|
||||
| Who should read this | Usually `All Struts 2 developers and users`. Narrow it only when exposure is genuinely conditional. |
|
||||
| Impact of vulnerability | A short impact phrase, not a paragraph — `Remote Code Execution`, `Denial of service`. Hedge where warranted (`Possible Remote Code Execution vulnerability`). |
|
||||
| Maximum security rating | `Low` / `Moderate` / `Important` / `Critical`, matching a definition on the [Security Bulletins](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=61758) page. That page is the only authority — the four-level naming postdates many older bulletins, so never calibrate against one. |
|
||||
| Recommendation | `Upgrade to Struts X.Y.Z at least`. Name **every** maintenance line carrying the fix, and add the required action where upgrading alone is not enough. |
|
||||
| Affected Software | Officially released versions only. One bullet per maintenance line; link the EOL announcement for end-of-life ranges. |
|
||||
| Reporters | Credit the reporter. Include their organisation where they gave one; obfuscate any email address. |
|
||||
| CVE Identifier | `CVE-YYYY-NNNNN (to be assigned before publication)` until the real identifier arrives. One CVE per independently fixable issue. |
|
||||
|
||||
### Affected Software
|
||||
|
||||
List only versions that passed a PMC release vote. A build that was cut, failed its
|
||||
test period and was superseded never reached users — listing it implies an official
|
||||
artifact was vulnerable and drags a phantom version into every downstream CVE record
|
||||
and scanner database. Do not read the range off git tags; tags exist for builds that
|
||||
were never voted through.
|
||||
|
||||
To find the lower bound: locate when the vulnerable construct entered with
|
||||
`git log -S`, then map that commit to the first release containing it. A defect often
|
||||
predates the control that was supposed to bound it, so do not assume it arrived with
|
||||
the feature that made it reachable. If the mapping cannot be settled, write a visible
|
||||
placeholder naming what must be confirmed — never a guessed version number.
|
||||
|
||||
## Problem
|
||||
|
||||
One to three sentences. Write for an operator, not a reviewer: the feature, the
|
||||
failure, the consequence.
|
||||
|
||||
| Safe to publish | Never publish before the fix is out |
|
||||
|---|---|
|
||||
| Impact categories and consequence | Class, method, or field names |
|
||||
| The component in plain words, linked to its documentation | `file:line` references |
|
||||
| Whether a configured control fails to apply | Commit hashes, PR or Jira numbers |
|
||||
| That state is shared / input is unvalidated | The triggering request shape or payload |
|
||||
| Which released versions are affected | Reproduction steps, PoC, timing conditions |
|
||||
|
||||
Older bulletins explained causes and mitigations in far more detail, and that detail
|
||||
was used to build working exploits. The project deliberately stopped. **Mine the
|
||||
archive for structure and tone, never for depth.**
|
||||
|
||||
**Then say who is not affected.** An operator's first question is "does this reach
|
||||
me?" — answer it, or every reader must assume it does. One sentence, linked to the
|
||||
feature's documentation, either as a trailing note or folded into the opening clause.
|
||||
Name the optional plugin, the setting that must be switched on, the endpoint that must
|
||||
be mapped, or the unaffected sibling path. Add "earlier releases are not affected"
|
||||
where there is a clean prior baseline.
|
||||
|
||||
Keep it at the level of a deployment decision, not a code path. Scoping *reduces* net
|
||||
disclosure: it shrinks the population that has to care, and costs an attacker nothing
|
||||
they could not read off a dependency list.
|
||||
|
||||
## Solution
|
||||
|
||||
`Upgrade to Struts X.Y.Z at least.` Repeat for each maintenance line, and link the
|
||||
migration guide where the fix requires one.
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
**Subject to the same disclosure budget as Problem.** This is the section that leaks:
|
||||
describing what changed about the fixed behaviour describes the defect. Write it in
|
||||
terms of what an application might *observe*, never what the fix altered internally,
|
||||
and derive it from the fix diff rather than the commit message — a summary calling the
|
||||
behaviour unchanged can still carry an observable difference.
|
||||
|
||||
It is also where a **breaking** upgrade is announced, and that announcement has to be
|
||||
blunt: what must be rewritten, and what staying put costs. Where the fix is
|
||||
transparent, the house sentence is simply `This change is backward compatible.`
|
||||
|
||||
## Workaround
|
||||
|
||||
Three valid outcomes, in order of preference:
|
||||
|
||||
1. **A verified configuration or operational change** — traced in source and confirmed
|
||||
to remove reachability. Give the change, not the mechanism. It need not be a Struts
|
||||
setting; container and reverse-proxy limits count, as does pointing at the relevant
|
||||
section of the Security Guide.
|
||||
2. **Upgrade only**, when you checked and found nothing.
|
||||
3. **Verified absence.** The house value is a bare `n/a`; spell it out when the reason
|
||||
is worth stating.
|
||||
|
||||
Never ship a workaround you reasoned about but did not confirm — it leaves operators
|
||||
believing they are protected and discredits every other field on the page. "No
|
||||
workaround exists" is a claim about absence and needs checking too.
|
||||
|
||||
A workaround usually reveals which path is affected. That is often the right trade,
|
||||
but make it deliberately and record which way you went.
|
||||
|
||||
## Before publishing
|
||||
|
||||
- [ ] Every placeholder is replaced, and no guidance text survives on the page.
|
||||
- [ ] The CVE identifier is real, not the placeholder.
|
||||
- [ ] Affected Software lists voted releases only, and covers every maintenance line.
|
||||
- [ ] The rating matches a published definition rather than an approximation.
|
||||
- [ ] The workaround was verified in source, or its absence was.
|
||||
- [ ] Problem, Backward compatibility and Workaround name no class, file, commit, PR
|
||||
or payload.
|
||||
- [ ] The fix is **merged** into the release branch — reviewed is not merged; re-check
|
||||
now, not at drafting time.
|
||||
- [ ] The fixed release is out and accepted.
|
||||
- [ ] Restrictions are lifted only at the coordinated publication moment.
|
||||
|
||||
## Storage-format skeleton
|
||||
|
||||
Ready to POST to the Confluence API. Give the `excerpt` macro a **fresh**
|
||||
`ac:macro-id` each time — two bulletins must not share one.
|
||||
|
||||
```xml
|
||||
<h2>Summary</h2>
|
||||
<ac:structured-macro ac:name="excerpt" ac:schema-version="1">
|
||||
<ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
|
||||
<ac:rich-text-body><p>ONE-LINE DESCRIPTION OF THE DEFECT</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<p class="auto-cursor-target"><br/></p>
|
||||
<table class="wrapped"><colgroup><col/><col/></colgroup><tbody>
|
||||
<tr><th><p>Who should read this</p></th><td><p>All Struts 2 developers and users</p></td></tr>
|
||||
<tr><th><p>Impact of vulnerability</p></th><td><p>IMPACT PHRASE</p></td></tr>
|
||||
<tr><th><p>Maximum security rating</p></th><td><p>Low | Moderate | Important | Critical</p></td></tr>
|
||||
<tr><th><p>Recommendation</p></th><td><p>Upgrade to Struts X.Y.Z at least</p></td></tr>
|
||||
<tr><th><p>Affected Software</p></th><td><ul style="list-style-type: square;">
|
||||
<li>Struts A.B.C through Struts D.E.F</li></ul></td></tr>
|
||||
<tr><th><p>Reporters</p></th><td><p>REPORTER</p></td></tr>
|
||||
<tr><th><p>CVE Identifier</p></th><td><p>CVE-YYYY-NNNNN (to be assigned before publication)</p></td></tr>
|
||||
</tbody></table>
|
||||
<h2>Problem</h2>
|
||||
<p>WHAT THE DEFECT ALLOWS, IN OPERATOR TERMS.</p>
|
||||
<p>WHO IS NOT AFFECTED, AND WHY.</p>
|
||||
<h2>Solution</h2>
|
||||
<p>Upgrade to Struts X.Y.Z at least.</p>
|
||||
<h2>Backward compatibility</h2>
|
||||
<p>This change is backward compatible.</p>
|
||||
<h2>Workaround</h2>
|
||||
<p>WORKAROUND, OR A STATEMENT THAT NONE EXISTS.</p>
|
||||
```
|
||||
@@ -0,0 +1,325 @@
|
||||
---
|
||||
name: creating-version-notes
|
||||
description: Use when preparing, updating, or reviewing the release documentation for a Struts release or release candidate on any maintenance line (6.x, 7.x) - the Version Notes page on the cwiki, its Migration Guide entry, the GitHub release notes, and the test-build announcement mail.
|
||||
---
|
||||
|
||||
# Creating Version Notes
|
||||
|
||||
## Overview
|
||||
|
||||
A Version Notes page answers one question for a user deciding whether to upgrade: **what changed in this release, and what will break.** Almost all of it is a mechanical rendering of a JIRA fix version onto fixed boilerplate.
|
||||
|
||||
**Core principle:** the mechanical parts must be *derived*, never retyped; the two judgement parts — Breaking changes, and how a security fix is described — are the only places you author prose.
|
||||
|
||||
**One skill covers every maintenance line.** 6.x and 7.x pages share an identical structure. The line changes the data (version, prior page, JIRA ids), never the process.
|
||||
|
||||
## The Iron Rule
|
||||
|
||||
```
|
||||
START FROM THE TEMPLATE. NEVER CLONE THE PREVIOUS VERSION NOTES PAGE.
|
||||
```
|
||||
|
||||
Cloning is how the published pages acquired their defects, and it fails differently every time:
|
||||
|
||||
| Page | Inherited defect |
|
||||
|---|---|
|
||||
| Version Notes 6.9.0 | Issue Detail links **"JIRA Release Notes 6.8.0"** — label and `version=` id both from 6.8.0 |
|
||||
| Version Notes 6.10.0 | Issue List links **"Struts 6.9.0 DONE"** — label names the previous release, against a `filter=` id different from the one the 6.9.0 page used |
|
||||
| Both series | Maven Dependency code macro carries `ac:name=""` instead of `ac:name="language"` |
|
||||
|
||||
Half-updated links are the signature failure: the number gets fixed and the label doesn't, or the reverse. They survive review because the link still works — it just points at, or claims to be, the wrong release.
|
||||
|
||||
**[`version-notes-template.md`](version-notes-template.md) is the source of truth**: field guidance, storage-format skeleton with those defects corrected, and the pre-publication checklist.
|
||||
|
||||
## Collect every input before writing
|
||||
|
||||
Each row is derived from a named source. A value you cannot source is a visible placeholder, never a guess.
|
||||
|
||||
| Input | Where it comes from |
|
||||
|---|---|
|
||||
| Version | The release being voted or announced |
|
||||
| Parent page | Always **Migration Guide** (page id `13981`) — every Version Notes page is a child of it |
|
||||
| Prior notes page title | The previous **released** version in the same series — see below |
|
||||
| JIRA version id | Numeric id behind `ReleaseNote.jspa?version=` — from the WW project's versions, **not** the version name |
|
||||
| DONE filter id | The saved JIRA filter for this release; a new release needs a new filter |
|
||||
| Issue list | `project = WW AND fixVersion = <version>`, grouped by type |
|
||||
| Breaking changes | Authored — see below |
|
||||
| Staging Repository block | An explicit decision — see below |
|
||||
|
||||
## The issue list
|
||||
|
||||
Group under `<h2>` per issue type, in this order, omitting any type with no issues:
|
||||
|
||||
**Bug → New Feature → Improvement → Task → Dependency**
|
||||
|
||||
Within a section, order by issue key ascending. Each entry is `[WW-XXXX] - <the JIRA summary verbatim>`.
|
||||
|
||||
**Reconcile against what actually merged.** The JIRA query is the starting point, not the answer. Three mismatches to check:
|
||||
|
||||
- A ticket resolved `Fixed` whose change did not make the release branch — it must not be listed as delivered.
|
||||
- A ticket resolved **`Won't Do`** or otherwise not `Fixed` — it belongs under `Rejected requests`, not in a type section and not dropped. Check the resolution, not just the status: both `Closed` and `Resolved` sit in the Done category.
|
||||
- Work that shipped under a ticket assigned to a different fix version — the notes under-report the release.
|
||||
|
||||
A ticket with no commit in the range is not automatically wrong. Check its **component** first: `IDEA Plugin`, `Example Applications` and similar live in other repositories and are still legitimately part of the release.
|
||||
|
||||
**Reconcile through the ticket's linked PR, reading the files it changed.** Do not grep commit subjects, and do not go looking for the class named in the ticket title: a title often names the *symptom* while the fix lives elsewhere. WW-5630 reads "Performance Issue SecurityMemberAccess" and was fixed in `ConfigParseUtil`; searching for the former concludes, wrongly, that the backport is missing. Squash-merges also rewrite hashes, so the merge commit id from the PR need not appear on the branch.
|
||||
|
||||
**Untick eted patch-level dependency bumps are not a gap.** Dependabot PRs for patch updates are merged directly and deliberately get no ticket, so they get no entry — there is nothing to link. Expect the pom to show a higher patch version than the ticket text says: 6.11.0 shipped jackson 2.22.1 while WW-5648 reads "2.21.4 to 2.22.0". That is correct, not an omission. Minor and major bumps do get a ticket and do get listed.
|
||||
|
||||
Where a ticket's summary was written for triage rather than for users, the page may carry a clearer summary — but then it is authored text, and the link must still resolve to that ticket.
|
||||
|
||||
## Only released versions belong in the chain
|
||||
|
||||
The prior-notes link forms a chain through the series, and it **skips versions that were cut but never released**. Version Notes 7.2.1 links back to 7.1.1, not to the withdrawn 7.2.0.
|
||||
|
||||
When a release is superseded before it ships, its content does not disappear — the successor absorbs it. 7.2.1 carries the Breaking changes for the whole 7.2.x cycle. Check what the predecessor covered before assuming your issue list is complete.
|
||||
|
||||
This is the same discipline `creating-security-bulletins` applies to Affected Software, for the same reason: naming a version that never reached users misdirects everyone downstream.
|
||||
|
||||
## Page section order
|
||||
|
||||
Beyond the boilerplate, sections appear in this order, each omitted when empty:
|
||||
|
||||
**Breaking changes → Deprecations → Rejected requests → Bug → New Feature → Improvement → Task → Dependency → Issue Detail → Issue List → Other resources**
|
||||
|
||||
The first three are authored; the issue-type sections are derived from JIRA.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
Present only when the release has them — a maintenance release usually does not.
|
||||
|
||||
Each item is **one sentence plus its ticket link**:
|
||||
|
||||
```
|
||||
<what changed, in terms of what an application sees> [WW-XXXX].
|
||||
```
|
||||
|
||||
> Annotated wildcard actions are matched most-specific-first, so action selection can differ [WW-3784].
|
||||
|
||||
> `JSONInterceptor` uses a fresh reader and writer per request, so custom ones must not hold state between requests [WW-5650].
|
||||
|
||||
The sentence exists so a reader can judge **whether to open the ticket**, not so they can avoid opening it. The ticket carries the detail — API signatures, migration steps, the config that changes. Naming the affected type or setting is enough; enumerating what replaces it is the ticket's job.
|
||||
|
||||
Derive each item from the fix diff rather than the ticket title, and write only what you confirmed. A change you suspect is breaking but could not pin down is one to raise with the release manager, not to describe vaguely.
|
||||
|
||||
## Deprecations
|
||||
|
||||
Where a release deprecates public API, list it separately from Breaking changes — nothing stops working yet, so mixing the two overstates the upgrade cost. Same one-line shape, naming the replacement where there is one:
|
||||
|
||||
> `ConversionRule.COLLECTION` and the `Collection_` key prefix are deprecated; use `ConversionRule.ELEMENT` and `Element_` instead [WW-5656].
|
||||
|
||||
## Rejected requests
|
||||
|
||||
A ticket resolved **`Won't Do`** (or otherwise not `Fixed`) against this fix version is still news: someone asked for it and the project decided against it.
|
||||
|
||||
- **Do not put it in a type section.** Under Improvement or New Feature it reads as delivered.
|
||||
- **Do not silently drop it either.** The decision is the value.
|
||||
- List it under `Rejected requests`, saying it will not be implemented and, where the release manager gave one, the reason.
|
||||
|
||||
> [WW-2635] - Flash scope - will not be implemented; the proposed mechanism could introduce a security risk.
|
||||
|
||||
Note the JIRA-generated release notes linked from the page *will* still include these tickets under their type. Clearing the fix version in JIRA is the only way to change that, and is the release manager's call.
|
||||
|
||||
## Security fixes in a release
|
||||
|
||||
A release usually ships before its bulletin publishes and before a CVE exists. The Version Notes then list a **public, neutrally-framed** ticket for a defect whose advisory is still restricted.
|
||||
|
||||
- List the ticket as you would any other. It is already public; omitting it under-reports the release.
|
||||
- **Do not add security framing the bulletin has not published yet** — no severity, no attack description, no S2-XXX or CVE number that has not been assigned and published.
|
||||
- Once the bulletin is public, the notes may link it.
|
||||
|
||||
**Where the ticket's own summary describes the defect, list the neutral part of it.** "List the summary verbatim" assumes a neutrally-worded ticket, and security tickets often are not. WW-5643 reads *"StrutsJSONReader parse state shared across concurrent requests — maxDepth bypass and cross-request data leak"*; the page carried it up to "concurrent requests" and stopped. The trailing clause is the bulletin's job.
|
||||
|
||||
Truncate at the clause boundary — never paraphrase into something the ticket does not say, and never alter the ticket link. Then **tell the release manager which summaries you cut and why**: whether an already-public JIRA summary should be reproduced in full is their call, not yours, and it has to be made before the page goes up rather than edited afterwards.
|
||||
|
||||
**REQUIRED BACKGROUND:** where the wording of a security-relevant entry is in question, `creating-security-bulletins` governs what may be said and when.
|
||||
|
||||
## The Staging Repository block
|
||||
|
||||
**Include it.** The block points readers at ASF Nexus staging so they can test the artifacts before the vote closes, and it stays on the page afterwards.
|
||||
|
||||
Older 6.x pages (6.9.0, 6.10.0) lack it while the 7.x pages carry it. That is an artefact of cloning within each series, not a difference between the lines — 6.11.0 carries it.
|
||||
|
||||
## Link the new page from the Migration Guide
|
||||
|
||||
The page is not finished when it is created. **[Migration Guide](https://cwiki.apache.org/confluence/spaces/WW/pages/13981/Migration+Guide) (id `13981`) is both the parent page and the index**, and a Version Notes page that is not listed there is unreachable by anyone browsing.
|
||||
|
||||
Add an entry at the **top** of the list under the `<h2>` for the matching line — `Version Notes 7.x`, `Version Notes 6.x`, and so on. The lists are newest-first, and the entry is a page link carrying no body text:
|
||||
|
||||
```xml
|
||||
<li><ac:link><ri:page ri:content-title="Version Notes X.Y.Z"/></ac:link></li>
|
||||
```
|
||||
|
||||
**Update the section, not the whole page.** `confluence_update_page_section` on the exact heading replaces only that section's body; its boundary is the next `<h2>`, so the section body includes the `<h3>` migration-guide link that follows the list. Supply that `<h3>` and its paragraph in the replacement content or they are dropped.
|
||||
|
||||
**Verify against raw storage, not the diff.** A version diff of this page renders empty even for a real change, because the markdown view discards `ac:link` bodies. Fetch the new version with `convert_to_markdown=false` and confirm the new entry is present, the prior entries survive in order, and the trailing `<h3>` appears exactly once.
|
||||
|
||||
This applies to **every** section update, including ones on the Version Notes page itself — shortening `Breaking changes` carries the same risk of swallowing the `Deprecations` heading that follows it. After any section write, confirm the sections below it are still present exactly once. Where the page has no `ac:link` in it, the cheaper markdown fetch is enough to see the headings.
|
||||
|
||||
## Writing pages through the API
|
||||
|
||||
`content_file` is rejected for any path outside the repository — a scratchpad path fails as path traversal. Draft wherever you like, but **pass the body as inline `content`** when creating or updating a page.
|
||||
|
||||
The response carries the new version number. On a page you have just written, that number is its own check: a create followed by one update should report version 2, so anything higher means someone else wrote in between.
|
||||
|
||||
## The GitHub release notes
|
||||
|
||||
A release also has a GitHub release at the `STRUTS_X_Y_Z` tag, kept as a **pre-release** while the vote runs. GitHub's generated body is a starting point that needs two corrections before it is fit to publish.
|
||||
|
||||
### Name the previous tag yourself
|
||||
|
||||
**Never let GitHub choose the range.** It picks the previous tag by reachability, and Struts release branches get renamed and re-imported, so older tags are frequently *not* ancestors of the new one and the heuristic reaches too far back. For 6.11.0 it chose `STRUTS_6_8_0` and produced ~101 entries, 88 of which had already shipped in 6.9.0 and 6.10.0.
|
||||
|
||||
Generate the body with the previous release named explicitly, and it comes out right the first time:
|
||||
|
||||
```bash
|
||||
gh api -X POST repos/apache/struts/releases/generate-notes \
|
||||
-f tag_name=STRUTS_7_3_0 -f previous_tag_name=STRUTS_7_2_1 -q .body > generated.md
|
||||
```
|
||||
|
||||
Confirm the entry count is plausible against the real change set, which `git log` gives even across unrelated histories:
|
||||
|
||||
```bash
|
||||
git log --format='%h %s' STRUTS_7_2_1..STRUTS_7_3_0
|
||||
```
|
||||
|
||||
**If you inherit a body GitHub generated on its own**, check the `**Full Changelog**: .../compare/<PREVIOUS>...<THIS>` line first, and regenerate as above rather than pruning by hand. When pruning is unavoidable, drop `## New Contributors` too if the contribution it cites falls outside the range — but keep it when the contributors are genuinely new in this range.
|
||||
|
||||
### Split the entries
|
||||
|
||||
Two sections, `### Dependencies` nested under `## What's Changed`, before any `## New Contributors`:
|
||||
|
||||
| Entry | Section |
|
||||
|---|---|
|
||||
| Carries a `WW-XXXX` ticket — whoever authored it | `## What's Changed` |
|
||||
| A human PR that is not a dependency change (ci, chore, release prep) | `## What's Changed` |
|
||||
| A dependency bump with **no** ticket | `### Dependencies` |
|
||||
|
||||
**The discriminator is the ticket, not the author.** A Dependabot PR carrying a ticket stays in What's Changed, because a ticketed bump is release content and appears in the Version Notes Dependency section. A human PR that is purely a dependency change (`Removes unused jaxb-core dependency`) belongs under Dependencies. Both cases occur in the 6.9.0 release.
|
||||
|
||||
A PR that mixes a dependency change with something else — CVE-driven library updates *plus* a CI tweak — stays in What's Changed. Dependencies is for entries that are nothing but a bump.
|
||||
|
||||
Preserve the generated relative order within each section, and keep the entry lines byte-identical — they carry the author and PR links GitHub rendered. Split with a script rather than by retyping, then **prove nothing was lost**:
|
||||
|
||||
```bash
|
||||
diff <(grep '^\* ' generated.md | sort) <(grep '^\* ' new.md | sort)
|
||||
```
|
||||
|
||||
Empty output means the entry set is unchanged and only the grouping moved.
|
||||
|
||||
### Applying it
|
||||
|
||||
The release may or may not exist yet — check before assuming which command you need.
|
||||
|
||||
```bash
|
||||
# it exists (release cut earlier, or notes already generated):
|
||||
gh release view STRUTS_X_Y_Z --json body -q .body > original.md # keep, so it can be restored
|
||||
gh release edit STRUTS_X_Y_Z --prerelease --notes-file new.md
|
||||
|
||||
# it does not exist yet:
|
||||
gh release create STRUTS_X_Y_Z --title "Struts X.Y.Z" --prerelease --verify-tag --notes-file new.md
|
||||
```
|
||||
|
||||
Pass `--prerelease` either way, so a release still under vote is not silently promoted, and `--verify-tag` on create so a typo in the tag fails instead of creating one.
|
||||
|
||||
## The test-build announcement
|
||||
|
||||
Once the Version Notes page and the GitHub release are both up, the release manager announces the test build so people can exercise the staged artifacts during the vote. **Draft it last** — every link in it points at something the earlier steps produced.
|
||||
|
||||
Subject is `[TEST] Apache Struts X.Y.Z test build is ready`. Send it to **both** lists, Bcc the private one:
|
||||
|
||||
```
|
||||
To: dev@struts.apache.org, user@struts.apache.org
|
||||
Bcc: private@struts.apache.org
|
||||
```
|
||||
|
||||
Both audiences want it — committers to check the staged artifacts, users to test against their own applications — and a build announced to only one of them reaches half the people who could find a problem during the vote.
|
||||
|
||||
The body is fixed apart from four substitutions:
|
||||
|
||||
```
|
||||
Hello,
|
||||
|
||||
This is a minor release of Struts <LINE> which contains <WHAT>, and it
|
||||
shouldn't break your code<RISK>. Please take your time and test the bits
|
||||
- any help is appreciated. Please report any problems you will spot.
|
||||
|
||||
Here are the changes from the previous version:
|
||||
https://github.com/apache/struts/releases/tag/STRUTS_X_Y_Z
|
||||
|
||||
Staging Maven repo
|
||||
https://repository.apache.org/content/groups/staging/
|
||||
|
||||
Standalone artifacts
|
||||
https://dist.apache.org/repos/dist/dev/struts/X.Y.Z/
|
||||
|
||||
Release notes
|
||||
https://cwiki.apache.org/confluence/display/WW/Version+Notes+X.Y.Z
|
||||
|
||||
Kind regards
|
||||
--
|
||||
Łukasz
|
||||
```
|
||||
|
||||
| Slot | How to fill it |
|
||||
|---|---|
|
||||
| `<LINE>` | `6.x` or `7.x` |
|
||||
| `<WHAT>` | What the issue list actually contains — `mostly bug fixes` for 6.11.0, `a few improvements and bug fixes` for 7.3.0 |
|
||||
| `<RISK>` | Empty when the release has no Breaking changes; ` but it contains significant changes` when it does. 6.11.0 had none and said nothing; 7.3.0 had seven and said so |
|
||||
| Tag / paths | Tag underscored (`STRUTS_7_3_0`), dist path and page title dotted (`7.3.0`) |
|
||||
|
||||
Do not take the recipients from a previous announcement: 6.11.0 went to `dev@` alone and 7.3.0 to `user@` alone, and both were mistakes. Address every announcement to the two lists above.
|
||||
|
||||
Keep the security posture of the pages: the mail links the release notes, it does not summarise what is in them, so no severity, CVE or S2-XXX reaches it either.
|
||||
|
||||
## Re-read the page immediately before you write to it
|
||||
|
||||
Confluence has no conflict warning. Fetch the current version immediately before every write and compare the version number against the one you read; if it advanced, re-read, merge onto the newer content, and write that.
|
||||
|
||||
After writing, diff against the version you meant to build on. The diff should show only your intended change.
|
||||
|
||||
## Red Flags — STOP
|
||||
|
||||
- Starting from a copy of the previous release's page
|
||||
- A version number or JIRA id typed rather than derived
|
||||
- A link whose label and its id name different releases
|
||||
- The prior-notes link pointing at a version that was cut but never released
|
||||
- Publishing the issue list straight from JIRA without reconciling against the release branch
|
||||
- Concluding a backport is missing from a commit-subject grep, or from the class named in the ticket title
|
||||
- Treating an untick eted patch dependency bump as a reconciliation gap
|
||||
- Dropping a `Won't Do` ticket, or listing it under Improvement or New Feature as though it shipped
|
||||
- A Breaking changes item that runs past one sentence, or restates what the ticket already explains
|
||||
- Reproducing a security ticket's summary in full when it names the bypass or the leak
|
||||
- Letting GitHub pick the previous tag instead of passing `previous_tag_name`
|
||||
- Regrouping release entries by retyping them instead of scripting the split and diffing the result
|
||||
- A severity, CVE, or S2-XXX reference on the page that has not been published
|
||||
- Breaking changes assembled by pasting ticket summaries
|
||||
- Creating the page without adding it to the Migration Guide index
|
||||
- Trusting an empty version diff on the Migration Guide as proof the edit landed
|
||||
- Publishing GitHub release notes without checking which tag the Full Changelog compares against
|
||||
- Splitting the GitHub sections by author instead of by whether the entry carries a ticket
|
||||
- Editing a GitHub release under vote without `--prerelease`
|
||||
- Writing from page content read earlier in the session without re-fetching
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
| Mistake | Reality |
|
||||
|---|---|
|
||||
| "Copying last release's page is faster" | It is how "JIRA Release Notes 6.8.0" shipped on the 6.9.0 page. Copy the template. |
|
||||
| "I updated the link, it's fine" | Check the label too. Every observed defect is a half-updated link. |
|
||||
| "`version=` takes the version number" | It takes JIRA's numeric version id. Look it up. |
|
||||
| "The DONE filter can be reused" | A reused filter shows the previous release's issues under this release's heading. |
|
||||
| "JIRA is the release contents" | JIRA is the claim. The release branch is the fact. Reconcile. |
|
||||
| "No commit mentions the ticket, so it wasn't backported" | Read the linked PR's changed files. Titles name symptoms, and squash-merges rewrite hashes. |
|
||||
| "The pom version doesn't match the ticket, that's a gap" | Patch bumps ship untick eted by design. Only ticketed bumps get an entry. |
|
||||
| "The page is created, so the work is done" | It is invisible until listed on the Migration Guide. |
|
||||
| "The version diff is empty, so nothing changed" | The diff renders markdown, which drops `ac:link` bodies. Check raw storage. |
|
||||
| "GitHub generated the changelog, so the range is right" | It guesses the previous tag by reachability. Renamed branches make it reach too far back. Verify with `git log PREV..THIS`. |
|
||||
| "Dependabot authored it, so it goes under Dependencies" | Ticketed bumps stay in What's Changed. The ticket decides, not the author. |
|
||||
| "A Won't Do ticket isn't part of the release" | The decision is news. It goes under Rejected requests, not into a type section and not into the bin. |
|
||||
| "More detail in Breaking changes is safer" | One sentence plus the ticket link. The reader opens the ticket for detail; the page exists to tell them whether to. |
|
||||
| "The summary is public in JIRA, so I can repeat it" | Not when it names the bypass or the leak and the bulletin is unpublished. Truncate, and say you did. |
|
||||
| "GitHub will work out the previous tag" | Pass `previous_tag_name` and it is right the first time. |
|
||||
| "The fix is public, so I can describe the vulnerability" | The ticket being public does not publish the advisory. Neutral framing until the bulletin ships. |
|
||||
| "Breaking changes are the tickets typed as breaking" | They are the changes that break an application. Author them. |
|
||||
| "7.x needs different handling from 6.x" | Same structure, same process. Only the data differs. |
|
||||
@@ -0,0 +1,152 @@
|
||||
# Version Notes Template
|
||||
|
||||
The canonical skeleton and per-field guidance for a Struts **Version Notes X.Y.Z** page
|
||||
on the [Apache Struts 2 Wiki](https://cwiki.apache.org/confluence/spaces/WW) (space `WW`).
|
||||
Companion to [`SKILL.md`](SKILL.md), which covers *how* to establish the values;
|
||||
this file covers *what the page contains*.
|
||||
|
||||
**This file is the source of truth.** Start every page from the skeleton below, never
|
||||
from a copy of the previous release's page — see the Iron Rule in `SKILL.md`.
|
||||
|
||||
## Fields
|
||||
|
||||
| Field | What goes in it |
|
||||
|---|---|
|
||||
| Version | The release being announced, e.g. `6.11.0`. Appears in the intro sentence, the page title, the Maven snippet, and both JIRA link labels. |
|
||||
| Parent page | Always `Migration Guide`, page id `13981`. Create the page as its child, and add it to that page's index — see `SKILL.md`. |
|
||||
| Prior notes page | Title of the previous **released** version's page in the same series, e.g. `Version Notes 6.10.0`. Skip versions that were cut but never released. |
|
||||
| JIRA version id | The numeric id for `ReleaseNote.jspa?version=`. Obtain from the WW project's versions — it is not the version name. `6.10.0` is `12357065`, `7.2.1` is `12355751`. |
|
||||
| DONE filter id | Saved-filter id for `issues/?filter=`, labelled `Struts X.Y.Z DONE`. Each release needs its own; a reused id lists the wrong release. |
|
||||
| TODO filter id | Constant across releases: `12351174`, labelled `Struts x.x.x TODO`. |
|
||||
| Issue sections | One `<h2>` per issue type present, ordered **Bug → New Feature → Improvement → Task → Dependency**, entries sorted by key ascending. |
|
||||
| Breaking changes | Optional. Authored, **one sentence plus the ticket link** per item. Omit the section when the release has none. |
|
||||
| Deprecations | Optional. Same one-line shape, for public API deprecated but still working. |
|
||||
| Rejected requests | Optional. Tickets resolved `Won't Do` against this fix version — never in a type section. |
|
||||
| Staging Repository | Always included, on every line — see `SKILL.md`. |
|
||||
|
||||
## Corrected storage format
|
||||
|
||||
Three defects present in the published pages are fixed here. Keep them fixed:
|
||||
|
||||
1. **`ac:name="language"` on the code macros.** The published Maven Dependency and
|
||||
Staging Repository macros carry `ac:name=""` with the value `xml`, which is a
|
||||
malformed parameter. The Archetype Catalog macro on the same pages has it right.
|
||||
2. **No `ac:macro-id` attributes.** The published pages share hard-coded macro ids
|
||||
across releases and across series because they were cloned. Omit the attribute and
|
||||
let Confluence assign one on save.
|
||||
3. **No trailing empty `<div>`s.** Every published page ends with two empty divs
|
||||
carrying inline `font-size: 24.0px` styling. They render as stray whitespace.
|
||||
|
||||
```xml
|
||||
<p><ac:emoticon ac:name="tick"/> These are the notes for the Struts version X.Y.Z distribution.</p>
|
||||
<p><ac:emoticon ac:name="tick"/> For prior notes in this release series, see <ac:link><ri:page ri:content-title="Version Notes PRIOR"/></ac:link></p>
|
||||
<p><ac:structured-macro ac:name="toc" ac:schema-version="1"/></p>
|
||||
|
||||
<h2>Maven users</h2>
|
||||
<p>If you are a Maven user, you might want to get started using the <ac:link><ri:page ri:content-title="Struts 2 Maven Archetypes"/><ac:plain-text-link-body><![CDATA[Maven Archetype]]></ac:plain-text-link-body></ac:link>.</p>
|
||||
<ac:structured-macro ac:name="code" ac:schema-version="1">
|
||||
<ac:parameter ac:name="title">Maven Dependency</ac:parameter>
|
||||
<ac:parameter ac:name="language">xml</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[<dependency>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<version>X.Y.Z</version>
|
||||
</dependency>
|
||||
]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>You can also use Struts Archetype Catalog like below</p>
|
||||
<ac:structured-macro ac:name="code" ac:schema-version="1">
|
||||
<ac:parameter ac:name="language">text</ac:parameter>
|
||||
<ac:parameter ac:name="title">Struts Archetype Catalog</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[mvn archetype:generate -DarchetypeCatalog=http://struts.apache.org/]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
|
||||
<ac:structured-macro ac:name="code" ac:schema-version="1">
|
||||
<ac:parameter ac:name="title">Staging Repository</ac:parameter>
|
||||
<ac:parameter ac:name="language">xml</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[<repositories>
|
||||
<repository>
|
||||
<id>apache.nexus</id>
|
||||
<name>ASF Nexus Staging</name>
|
||||
<url>https://repository.apache.org/content/groups/staging/</url>
|
||||
</repository>
|
||||
</repositories>]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
|
||||
<!-- OPTIONAL: omit the whole section when the release has no breaking changes -->
|
||||
<h2>Breaking changes</h2>
|
||||
<ul style="list-style-type: square;">
|
||||
<li>ONE SENTENCE: WHAT AN APPLICATION SEES DIFFERENTLY [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
|
||||
</ul>
|
||||
|
||||
<!-- OPTIONAL: public API deprecated but still working -->
|
||||
<h2>Deprecations</h2>
|
||||
<ul style="list-style-type: square;">
|
||||
<li><code>WHAT</code> is deprecated; use <code>REPLACEMENT</code> instead [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
|
||||
</ul>
|
||||
|
||||
<!-- OPTIONAL: tickets resolved Won't Do against this fix version -->
|
||||
<h2>Rejected requests</h2>
|
||||
<p>Two long-standing requests were closed as <em>Won't Do</em> in this cycle. They are listed here so the decision is visible rather than silent.</p>
|
||||
<ul style="list-style-type: square;">
|
||||
<li>[<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>] - SUMMARY - will not be implemented; REASON WHERE THE RELEASE MANAGER GAVE ONE.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Bug</h2>
|
||||
<ul><li>[<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>] - JIRA SUMMARY</li></ul>
|
||||
|
||||
<h2>Issue Detail</h2>
|
||||
<ul><li><a href="https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12311041&version=JIRA_VERSION_ID">JIRA Release Notes X.Y.Z</a></li></ul>
|
||||
|
||||
<h2>Issue List</h2>
|
||||
<ul>
|
||||
<li><a href="https://issues.apache.org/jira/issues/?filter=DONE_FILTER_ID">Struts X.Y.Z DONE</a></li>
|
||||
<li><a href="https://issues.apache.org/jira/issues/?filter=12351174">Struts x.x.x TODO</a></li>
|
||||
</ul>
|
||||
|
||||
<h2>Other resources</h2>
|
||||
<ul>
|
||||
<li><a href="http://www.mail-archive.com/commits%40struts.apache.org/">Commit Logs</a></li>
|
||||
<li><a href="https://gitbox.apache.org/repos/asf?p=struts.git;a=summary">Source Code Repository</a></li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Repeat the issue `<h2>` block per type present, in the order given above.
|
||||
`projectId=12311041` is the WW project and is constant. Note `&` in the
|
||||
`ReleaseNote.jspa` URL — a bare `&` is invalid in storage format.
|
||||
|
||||
## Before publishing
|
||||
|
||||
- [ ] Every placeholder is replaced, and no guidance text survives on the page.
|
||||
- [ ] Page title is `Version Notes X.Y.Z` and the intro names the same version.
|
||||
- [ ] Prior-notes link resolves, and names the previous **released** version.
|
||||
- [ ] Maven snippet version matches the release.
|
||||
- [ ] `ReleaseNote.jspa` label and its `version=` id are the same release.
|
||||
- [ ] `DONE` filter label and its `filter=` id are the same release.
|
||||
- [ ] Issue list reconciled against the release branch via each ticket's linked PR, not taken from JIRA alone.
|
||||
- [ ] Every ticket's **resolution** checked, not just its status — `Won't Do` goes under Rejected requests.
|
||||
- [ ] Sections ordered Breaking changes → Deprecations → Rejected requests → Bug → New Feature → Improvement → Task → Dependency; empty ones omitted.
|
||||
- [ ] Each Breaking changes and Deprecations item is one sentence plus its ticket link.
|
||||
- [ ] Staging Repository block present.
|
||||
- [ ] No unpublished severity, CVE, or S2-XXX reference anywhere on the page, and any security summary truncated at a clause boundary was reported to the release manager.
|
||||
- [ ] Page created as a child of Migration Guide (`13981`).
|
||||
- [ ] **Listed at the top of the matching `Version Notes N.x` section on the Migration Guide**, and that edit verified against raw storage — the version diff renders empty even when the change landed.
|
||||
- [ ] Page re-fetched immediately before every write.
|
||||
|
||||
## GitHub release notes
|
||||
|
||||
- [ ] Body generated with `previous_tag_name` named explicitly, not left to GitHub's guess.
|
||||
- [ ] Entry count sane against `git log PREV..THIS`.
|
||||
- [ ] Original body saved first when editing an existing release, so it can be restored.
|
||||
- [ ] Entries split by **ticket, not author**: ticketed → `## What's Changed`; untick eted dependency bumps → `### Dependencies`; mixed PRs stay in What's Changed.
|
||||
- [ ] Generated order and entry text preserved within each section.
|
||||
- [ ] Split verified by diffing the sorted entry lists before and after — empty output.
|
||||
- [ ] `--prerelease` passed while the vote is open; `--verify-tag` when creating.
|
||||
|
||||
## Test-build announcement
|
||||
|
||||
- [ ] Drafted **after** the Version Notes page and GitHub release exist — it links both.
|
||||
- [ ] Subject `[TEST] Apache Struts X.Y.Z test build is ready`.
|
||||
- [ ] Addressed to **both** `dev@struts.apache.org` and `user@struts.apache.org`, Bcc `private@struts.apache.org`.
|
||||
- [ ] Risk clause matches reality: silent when there are no Breaking changes, "but it contains significant changes" when there are.
|
||||
- [ ] Tag underscored in the release link, version dotted in the dist path and page title.
|
||||
@@ -34,10 +34,18 @@ For each claim, independently verify:
|
||||
| "No mitigation / no gate exists" | Search for gates, filters, allowlists, authorizers *yourself* — absence claims are the most often wrong |
|
||||
| "Default configuration" | Check the **effective runtime default**, not one source (see trap below) |
|
||||
| "Same as CVE-XXXX" | Confirm the mechanism actually matches; analogy ≠ equivalence |
|
||||
| A working PoC | Trace whether the payload survives every filter on the path |
|
||||
| A working PoC | **Run it if it is runnable**, then trace whether the payload survives every filter on the path |
|
||||
|
||||
If the report has **no reproducible PoC against a default config**, that is itself a triage outcome — say so per `SECURITY.md`.
|
||||
|
||||
## Find the control case
|
||||
|
||||
A single odd behaviour is ambiguous — it can nearly always be read as intended. What settles it is the **sibling that behaves correctly under the same input**.
|
||||
|
||||
Before writing a verdict, find the case that ought to differ and check it: the annotated property beside the unannotated one, the ordinary setter beside the dynamic one, the sibling path the same control does cover. Behave alike and you are probably looking at a design decision. Diverge, and the control is incomplete — that divergence *is* the finding.
|
||||
|
||||
Prefer an executed differential to an argued one. An existing test that passes beside the reporter's failing one is the strongest evidence a triage can produce.
|
||||
|
||||
## The effective-default trap
|
||||
|
||||
A Java field initializer and the shipped config can disagree. Reading only one produces a confident, wrong claim.
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
language: [ 'java' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Java JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
@@ -53,12 +53,12 @@ jobs:
|
||||
java-version: 17
|
||||
cache: 'maven'
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4.36.2
|
||||
uses: github/codeql-action/init@v4.37.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4.36.2
|
||||
uses: github/codeql-action/autobuild@v4.37.3
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4.36.2
|
||||
uses: github/codeql-action/analyze@v4.37.3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -20,8 +20,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'develop'
|
||||
- 'release/*'
|
||||
- 'support/*'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions: read-all
|
||||
|
||||
@@ -31,9 +34,10 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test (JDK ${{ matrix.java }})${{ matrix.profile == '-Pjakartaee11' && ' with Jakarta EE 11' || matrix.profile }}
|
||||
name: Build and Test (JDK ${{ matrix.java }})${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11 + Spring 7)' || matrix.profile }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- java: '17'
|
||||
@@ -44,14 +48,27 @@ jobs:
|
||||
profile: '-Pjakartaee11'
|
||||
- java: '25'
|
||||
profile: ''
|
||||
- java: '25'
|
||||
profile: '-Pjakartaee11'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Java ${{ matrix.java }}
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: ${{ matrix.java }}
|
||||
cache: 'maven'
|
||||
- name: Maven Verify on Java ${{ matrix.java }}${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11)' || matrix.profile }}
|
||||
- name: Maven Verify on Java ${{ matrix.java }}${{ matrix.profile == '-Pjakartaee11' && ' (Jakarta EE 11 + Spring 7)' || matrix.profile }}
|
||||
run: mvn -B -V -DskipAssembly verify ${{ matrix.profile }} --no-transfer-progress
|
||||
|
||||
- name: Test Summary ${{ matrix.java }} ${{ matrix.profile }}
|
||||
uses: mikepenz/action-junit-report@d9f48fc87bc235f7e214acf696ca5abc0a986f16 #v6.4.2
|
||||
continue-on-error: true
|
||||
if: always()
|
||||
with:
|
||||
annotate_only: true # forked repo cannot write to checks so just do annotations
|
||||
report_paths: |
|
||||
**/surefire-reports/TEST-*.xml
|
||||
**/failsafe-reports/TEST-*.xml
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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.
|
||||
|
||||
name: OWASP checkup
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'develop'
|
||||
- 'release/*'
|
||||
- 'support/*'
|
||||
workflow_dispatch: #Allow manual triggers
|
||||
|
||||
permissions: read-all
|
||||
|
||||
env:
|
||||
MAVEN_OPTS: -Xmx2048m -Xms1024m
|
||||
LANG: en_US.utf8
|
||||
|
||||
jobs:
|
||||
|
||||
|
||||
owasp:
|
||||
name: OWASP
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
HAVE_NIST_NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY != '' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
- name: Setup Java 25
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 25
|
||||
cache: 'maven'
|
||||
|
||||
- name: Cache NVD Database
|
||||
id: cache-nvd
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ~/.m2/repository/org/owasp/dependency-check-data
|
||||
key: nvd-cache-${{ runner.os }}-owasp-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
nvd-cache-${{ runner.os }}-owasp-
|
||||
nvd-cache-${{ runner.os }}-
|
||||
|
||||
- name: OWASP Dependency check update cache via NIST_NVD_API_KEY
|
||||
id: nvd-api-update
|
||||
if: ${{ env.HAVE_NIST_NVD_API_KEY == 'true' }}
|
||||
continue-on-error: true
|
||||
run: mvn -N -V -DskipAssembly -Dmaven.test.skip=true -Powasp-nvd-api -Pdependency-update-only --no-transfer-progress
|
||||
env:
|
||||
NIST_NVD_API_KEY: ${{ secrets.NIST_NVD_API_KEY}}
|
||||
|
||||
- name: OWASP Dependency check update cache via Mirror
|
||||
if: ${{ env.HAVE_NIST_NVD_API_KEY == 'false' || steps.nvd-api-update.outcome == 'failure' }}
|
||||
run: mvn -N -V -DskipAssembly -Dmaven.test.skip=true -Powasp-nvd-mirror -Pdependency-update-only --no-transfer-progress
|
||||
|
||||
- name: Cache NVD Database
|
||||
uses: actions/cache/save@v6
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
path: ~/.m2/repository/org/owasp/dependency-check-data
|
||||
key: nvd-cache-${{ runner.os }}-owasp-${{ github.run_id }}
|
||||
|
||||
- name: OWASP check (Without running tests)
|
||||
run: mvn -B org.owasp:dependency-check-maven:aggregate -Pdependency-check -Pjakartaee11 -DautoUpdate=false --no-transfer-progress
|
||||
|
||||
- name: Upload Dependency Check reports
|
||||
uses: actions/upload-artifact@v7
|
||||
if: always()
|
||||
with:
|
||||
name: dependency-check
|
||||
path: target/dependency-check*
|
||||
|
||||
- name: Add OWASP summary
|
||||
if: always()
|
||||
run: |
|
||||
{
|
||||
echo "## OWASP Dependency Check"
|
||||
echo ""
|
||||
echo "The HTML report has been uploaded as the **dependency-check** artifact."
|
||||
echo "Download it from the Artifacts section of this workflow run."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -41,12 +41,12 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6 # 3.1.0
|
||||
uses: actions/checkout@v7 # 3.1.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # 2.4.3
|
||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # 2.4.4
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -65,6 +65,6 @@ jobs:
|
||||
retention-days: 5
|
||||
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@eda5730a8bfb740e03a28087a958444c646e5842 # 2.22.11
|
||||
uses: github/codeql-action/upload-sarif@f58f0d11ebf5dedd870fab2f999275f7602cfa46 # 2.22.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -26,6 +26,7 @@ permissions: read-all
|
||||
env:
|
||||
MAVEN_OPTS: -Xmx2048m -Xms1024m
|
||||
LANG: en_US.utf8
|
||||
HAVE_SONARCLOUD_TOKEN: ${{ secrets.SONARCLOUD_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
sonarcloud:
|
||||
@@ -33,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.pull_request.base.repo.fork && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v5
|
||||
@@ -41,7 +42,14 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
cache: 'maven'
|
||||
- env:
|
||||
- name: SonarCloud Scan
|
||||
if: ${{ env.HAVE_SONARCLOUD_TOKEN == 'true' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
|
||||
run: ./mvnw -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Pcoverage -DskipAssembly
|
||||
- name: SonarCloud Scan -- SKIPPED
|
||||
if: ${{ env.HAVE_SONARCLOUD_TOKEN != 'true' }}
|
||||
run: |
|
||||
echo "### SonarCloud not configured" >> $GITHUB_STEP_SUMMARY
|
||||
echo "secrets.SONARCLOUD_TOKEN not existing, cannot push coverage checks" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -13,8 +13,9 @@ Before drafting any report, opening an issue, posting publicly, or reaching a se
|
||||
- read the Struts security policy,
|
||||
- read the Struts security guidelines,
|
||||
- check previously disclosed vulnerabilities and Security Bulletins.
|
||||
2. **Assess** the finding against the questions in [`SECURITY.md` § Assessment](SECURITY.md#assessment). If the answers do not still point to a likely new
|
||||
framework vulnerability, stop and explain — do not draft a new report.
|
||||
2. **Assess** the finding against the questions in [`SECURITY.md` § Assessment](SECURITY.md#assessment), and route it through the disposition guide in
|
||||
[`THREAT_MODEL.md`](THREAT_MODEL.md) (scope, trust boundaries, in-model security properties, and the known-non-findings list). If the answers do not still
|
||||
point to a likely new framework vulnerability, stop and explain — do not draft a new report.
|
||||
3. **Report privately** to `security@struts.apache.org` following [`SECURITY.md` § Private Report Requirements](SECURITY.md#private-report-requirements) and
|
||||
[§ Report Quality Rules](SECURITY.md#report-quality-rules).
|
||||
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
<!---
|
||||
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.
|
||||
-->
|
||||
# Contributing to Apache Struts
|
||||
|
||||
Thanks for your interest in contributing! Apache Struts is maintained by a
|
||||
community of volunteers under the [Apache Software Foundation](https://www.apache.org/).
|
||||
This guide walks a first-time contributor from a fresh clone to a merged pull
|
||||
request. You do not need to be a committer to contribute — anyone can open a PR.
|
||||
|
||||
## Getting help
|
||||
|
||||
- **Mailing lists:** Subscribe and ask on the developer or user list — see
|
||||
<https://struts.apache.org/mail.html>. The developer list is the best place
|
||||
to discuss a change before you start larger work.
|
||||
- **Issue tracker:** [JIRA WW project](https://issues.apache.org/jira/projects/WW).
|
||||
- **Homepage & docs:** <https://struts.apache.org/>.
|
||||
|
||||
If you are unsure whether a change is wanted, ask on the developer list or
|
||||
comment on the relevant JIRA issue first.
|
||||
|
||||
## Project overview
|
||||
|
||||
Apache Struts is a mature MVC web framework for Java (originally WebWork 2). It
|
||||
uses OGNL for value-stack expressions and FreeMarker for UI tag templates. The
|
||||
repository is a multi-module Maven build:
|
||||
|
||||
| Module | Responsibility |
|
||||
|------------|-------------------------------------------------------------|
|
||||
| `core` | `struts2-core` — the main framework |
|
||||
| `plugins` | Plugin modules (json, rest, spring, tiles, velocity, …) |
|
||||
| `apps` | Sample applications (showcase, rest-showcase) |
|
||||
| `assembly` | Distribution packaging |
|
||||
| `bom` | Bill of Materials for dependency management |
|
||||
| `parent` | Parent POM with shared configuration |
|
||||
| `jakarta` | Jakarta EE compatibility modules |
|
||||
|
||||
The request lifecycle is `Dispatcher` → `ActionProxy` → `ActionInvocation` →
|
||||
interceptor stack → `Action` → `Result`.
|
||||
|
||||
## Prerequisites & building
|
||||
|
||||
- **JDK 17** and **Maven**.
|
||||
- Run the tests (skipping assembly for speed):
|
||||
|
||||
```bash
|
||||
mvn test -DskipAssembly
|
||||
```
|
||||
|
||||
- Run a single test in a specific module:
|
||||
|
||||
```bash
|
||||
mvn test -DskipAssembly -pl core -Dtest=MyClassTest#testMethodName
|
||||
```
|
||||
|
||||
- Build against the Jakarta EE 11 / Spring 7 profile:
|
||||
|
||||
```bash
|
||||
mvn clean install -Pjakartaee11
|
||||
```
|
||||
|
||||
Tests use JUnit 5 with AssertJ assertions and Mockito for mocking.
|
||||
|
||||
## Finding something to work on
|
||||
|
||||
Browse the [JIRA WW project](https://issues.apache.org/jira/projects/WW) for
|
||||
open issues. Comment on an issue to let others know you are working on it. If
|
||||
no ticket exists for your change, **file one first** — every commit and pull
|
||||
request must reference a `WW-XXXX` ticket ID.
|
||||
|
||||
## Development workflow
|
||||
|
||||
1. Fork the repository and clone your fork.
|
||||
2. Create a branch off `main` named after the ticket, e.g. `WW-1234-short-description`.
|
||||
3. Implement your change **with tests**. Keep commits focused.
|
||||
4. Prefix every commit message with the ticket ID: `WW-1234 Describe the change`.
|
||||
5. Run `mvn test -DskipAssembly` and make sure it passes before opening a PR.
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
- **Title format:** `WW-XXXX Description` (the JIRA ticket ID is required).
|
||||
- **Link the ticket** in the description:
|
||||
`Fixes [WW-XXXX](https://issues.apache.org/jira/browse/WW-XXXX)`.
|
||||
- Continuous integration must pass, and reviewers expect code changes to come
|
||||
with tests.
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
**Do not** open a public GitHub issue, JIRA issue, pull request, or
|
||||
mailing-list thread for a suspected vulnerability. Report it privately to
|
||||
**security@struts.apache.org**. See [`SECURITY.md`](SECURITY.md) for the full
|
||||
process. This includes OGNL injection, parameter-filtering bypasses, file
|
||||
upload exploits, authentication bypass, RCE, SSRF, path traversal,
|
||||
deserialization, and XSS in framework components.
|
||||
|
||||
## Licensing & Code of Conduct
|
||||
|
||||
- Apache Struts is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
- Every new source file must include the standard ASF license header (see any
|
||||
existing source file or this file's header for the exact text).
|
||||
- By submitting a pull request you agree to license your contribution under the
|
||||
Apache License 2.0. The ASF does not require a separate signed CLA for typical
|
||||
contributions.
|
||||
- All participation is governed by the
|
||||
[ASF Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
|
||||
+35
-3
@@ -1,5 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Threat Model
|
||||
|
||||
A structured threat model for the Apache Struts framework — scope, adversary model,
|
||||
the security properties the framework provides vs. leaves to the application, and a
|
||||
triage-disposition guide for inbound reports and automated-scanner findings — is
|
||||
maintained in [`THREAT_MODEL.md`](THREAT_MODEL.md). It is additive to this policy:
|
||||
this `SECURITY.md` and the [security guidance](https://struts.apache.org/security/)
|
||||
remain canonical for the reporting process and configuration details.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please visit the [Releases](https://struts.apache.org/releases.html#prior-releases) page to see full information about each version
|
||||
@@ -43,6 +52,28 @@ The mailing address is: [security@struts.apache.org](mailto:security@struts.apac
|
||||
|
||||
[The Apache Security Team](http://www.apache.org/security/)
|
||||
|
||||
## Do not disclose through a pull request, commit, or issue
|
||||
|
||||
**A fix is a disclosure.** Opening a public pull request, pushing a commit, branch, or
|
||||
fork, or filing a public Jira/GitHub issue that **fixes, describes, or hints at** a
|
||||
suspected vulnerability reveals where the weakness is — often with a working roadmap to
|
||||
exploit it — before a fixed release exists. This holds even if you never attach a
|
||||
proof-of-concept, and even if you believe the impact is low or you are "just hardening"
|
||||
the code.
|
||||
|
||||
If you have found, or suspect you have found, a security problem:
|
||||
|
||||
- **Do not** open a public PR, commit, branch, fork, Jira issue, or mailing-list thread
|
||||
for it.
|
||||
- **Do** email [security@struts.apache.org](mailto:security@struts.apache.org) first and
|
||||
wait for the PMC to triage it and agree how the fix will be handled — the fix is
|
||||
typically prepared privately and landed alongside the advisory and release.
|
||||
|
||||
If you notice a possible security issue while working on an unrelated bug or PR, stop and
|
||||
email the private list before pushing the change. **When in doubt, treat it as
|
||||
security-sensitive and email the list** — a private report that turns out to be a
|
||||
non-issue costs far less than a public change that turns out to be exploitable.
|
||||
|
||||
## Before Reporting
|
||||
|
||||
Before sending a vulnerability report, run through the following checks. They exist to prevent duplicate reports, public disclosure of untriaged issues,
|
||||
@@ -119,8 +150,9 @@ Do not speculate beyond what can be demonstrated. If severity is uncertain, say
|
||||
- One vulnerability per report.
|
||||
- Keep reproduction steps minimal and self-contained.
|
||||
- Do not include unrelated findings.
|
||||
- Do not publish exploit details or proof-of-concept code publicly before the Struts project has triaged the issue. **Pushing a PoC to a public GitHub
|
||||
repository, gist, fork, or branch counts as public disclosure** — even a "test" or throwaway repo. Private repositories are acceptable for sharing a PoC,
|
||||
but access must be granted individually to each PMC member who will triage the report.
|
||||
- Do not publish exploit details or proof-of-concept code publicly before the Struts project has triaged the issue. **A fix, patch, or hardening change is a
|
||||
public disclosure in the same way a PoC is** — see [Do not disclose through a pull request, commit, or issue](#do-not-disclose-through-a-pull-request-commit-or-issue).
|
||||
**Pushing a PoC to a public GitHub repository, gist, fork, or branch counts as public disclosure** — even a "test" or throwaway repo. Private repositories
|
||||
are acceptable for sharing a PoC, but access must be granted individually to each PMC member who will triage the report.
|
||||
- Do not send ordinary bugs, usage questions, or generic denial-of-service concerns to the private security list.
|
||||
- If the issue is not a vulnerability in Apache Struts source code, use the appropriate public support or issue channel instead.
|
||||
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
# Apache Struts — Threat Model (v0 draft)
|
||||
|
||||
## §1 Header
|
||||
|
||||
- **Project:** Apache Struts (`apache/struts`), `main` @ HEAD (2026-06). Scope: the
|
||||
Struts framework in `apache/struts` only (the core MVC framework, its
|
||||
interceptors, tags, and the plugins shipped in this repo).
|
||||
- **Date:** 2026-06-24. **Drafted for PMC review** via the threat-model-producer
|
||||
rubric (Scovetta). This is an unratified proposal, not an ASF Security team or
|
||||
PMC position; authorship and sponsorship are settled only once the PMC adopts it
|
||||
(see Status below and §14).
|
||||
- **Status:** DRAFT — not yet reviewed by the Struts PMC. Built as a strict
|
||||
superset of the existing [`SECURITY.md`](SECURITY.md) and the published
|
||||
[Struts security guidance](https://struts.apache.org/security/); every
|
||||
load-bearing claim is tagged for provenance (see §14 for open questions).
|
||||
- **Version binding:** versioned with the project; a report against version *N*
|
||||
is triaged against the model as it stood at *N*. The security envelope changed
|
||||
materially at **7.0** (several hardening knobs flipped to secure-by-default —
|
||||
§5a), so the version is itself load-bearing.
|
||||
- **Reporting cross-reference:** §8-property violations → report privately per
|
||||
[`SECURITY.md`](SECURITY.md) (`security@struts.apache.org`); §3/§9/§11a findings
|
||||
are closed citing this document and the existing `SECURITY.md` "Before
|
||||
Reporting" checks.
|
||||
- **Provenance legend:** *(documented)* = Struts' own docs/`SECURITY.md`/security
|
||||
site; *(maintainer)* = confirmed by a Struts PMC member through this process;
|
||||
*(inferred)* = reasoned from architecture/docs, not yet PMC-ratified — each has
|
||||
a matching §14 open question.
|
||||
- **Draft confidence:** the bulk is *(documented)* — Struts has an unusually rich
|
||||
published security policy — with a handful of *(inferred)* scoping calls for the
|
||||
PMC to ratify.
|
||||
|
||||
**What Struts is.** Apache Struts 2 is a **Java MVC web framework** for building
|
||||
server-side web applications. A request flows: servlet filter → action mapping →
|
||||
**interceptor stack** (parameter population, validation, etc.) → **Action** →
|
||||
**result** (typically a JSP/FreeMarker view). Request parameters are bound onto
|
||||
action properties via setters, and view/configuration expressions are evaluated
|
||||
through **OGNL (Object-Graph Navigation Language)** against the **ValueStack**.
|
||||
*(documented — struts.apache.org)*
|
||||
|
||||
**The framework's own security philosophy (load-bearing).** Struts
|
||||
**"doesn't provide any security mechanism — it is just a pure web framework."**
|
||||
*(documented — [security guidance](https://struts.apache.org/security/))* It is
|
||||
not an authentication, authorization, session-security, or input-sanitisation
|
||||
layer; those are the embedding application's responsibility (§3/§10). What Struts
|
||||
*does* take an active stance on is **not letting its own machinery — chiefly OGNL
|
||||
expression evaluation and request-parameter binding — become an injection vector**.
|
||||
That single sentence shapes the whole model: most "Struts is insecure" reports are
|
||||
either OGNL-injection-class (in model, §8) or application-responsibility (out of
|
||||
model, §3/§11a).
|
||||
|
||||
## §2 Scope and intended use
|
||||
|
||||
Intended deployment: the Struts JARs are a **dependency embedded inside a web
|
||||
application** (a WAR) that the application developer writes, configures, and
|
||||
deploys into a servlet container (Tomcat, Jetty, …) behind the operator's
|
||||
perimeter. Struts is **in-process** with the application; it has no daemon, no
|
||||
listening socket of its own, and no trust boundary against the application code
|
||||
it runs inside. *(documented — it is a framework, not a server.)*
|
||||
|
||||
**Caller roles.**
|
||||
|
||||
- **Untrusted HTTP client** — sends requests (parameters, headers, cookies,
|
||||
multipart uploads) to a Struts-backed endpoint. **The primary untrusted boundary.**
|
||||
Struts must treat all request-derived values as hostile. *(documented — the
|
||||
parameter/OGNL hardening exists precisely for this actor.)*
|
||||
- **Application developer** — writes the actions, JSPs, struts.xml/annotations,
|
||||
and chooses the hardening settings (§5a). **Trusted by the framework** — their
|
||||
code and configuration run with the application's privileges. A finding that
|
||||
requires the developer to write unsafe code or disable a default protection is
|
||||
the application's bug, not Struts' (§3). *(documented — the developer-responsibility
|
||||
section of the security guidance.)*
|
||||
- **Operator** — deploys the WAR, sets `devMode` off, restricts dev-only plugins,
|
||||
configures the container and JVM. **Trusted.** *(documented.)*
|
||||
|
||||
**Component families.**
|
||||
|
||||
| Family | Entry point | Touches | In model? |
|
||||
| --- | --- | --- | --- |
|
||||
| OGNL evaluation + ValueStack | expression eval for params, tags, results | in-JVM code paths | **In — the central attack surface** *(documented)* |
|
||||
| Parameter binding (`ParametersInterceptor`, `@StrutsParameter`) | request params → action setters | reflection into app objects | **In — primary boundary** *(documented)* |
|
||||
| Interceptor stack (cookie, fileupload, fetch-metadata, COOP/COEP, …) | per-request processing | request data | **In** *(documented)* |
|
||||
| Tag library / JSP & FreeMarker integration | view rendering, expression output | template eval | **In — output-side OGNL/EL** *(documented)* |
|
||||
| File upload (Jakarta multipart) | multipart request parsing | temp files | **In — historical CVE surface** *(documented — S2 bulletins)* |
|
||||
| Bundled plugins (REST, JSON, Convention, …) in this repo | extra mappers/result types | request data | **In — same request-trust surface** *(inferred — §14 Q-plugins)* |
|
||||
| Config Browser Plugin | exposes internal config | dev-only diagnostic | **In as dev-only** — exposure in prod is operator misconfig (§3/§11a) *(documented)* |
|
||||
| Embedding application's own actions/JSPs/config | the developer's code | as the app | **Out — application responsibility (§3)** *(documented)* |
|
||||
| Examples / showcase / test apps | demo code | n/a | **Out** *(see §3)* |
|
||||
|
||||
## §3 Out of scope (explicit non-goals)
|
||||
|
||||
The detailed lists of developer anti-patterns and insecure configurations are
|
||||
maintained in the project's own docs and are **not duplicated here** — this model
|
||||
links to them and assigns each a triage disposition (§13):
|
||||
|
||||
- **Anything the application developer is responsible for.** Struts provides no
|
||||
security mechanism of its own *(documented)*. The full enumeration —
|
||||
developer-exposed unsafe setters, request parameters used in localization or
|
||||
forced OGNL evaluation, raw `${...}` JSP-EL over untrusted values, direct JSP
|
||||
access, mixing security levels in one namespace — is in the
|
||||
[security guidance](https://struts.apache.org/security/) and
|
||||
[`SECURITY.md`](SECURITY.md). All are `OUT-OF-MODEL: application-responsibility`.
|
||||
- **Findings that only manifest with a documented-insecure / non-default setting**
|
||||
(`devMode=true`, Config Browser Plugin exposed in production, DMI enabled, or a
|
||||
§5a hardening knob turned off) → `OUT-OF-MODEL: non-default-config`. *(documented.)*
|
||||
- **The servlet container, JVM, JDK, and OS**, and the application's own
|
||||
authentication, authorization, session management, CSRF token storage, and
|
||||
transport (TLS). Struts is "a pure web framework," not a security framework.
|
||||
*(documented / inferred — §14 Q-env.)*
|
||||
- **Generic denial of service.** Per [`SECURITY.md`](SECURITY.md), generic flooding
|
||||
or large-body streaming is not accepted; only *super-linear* amplification inside
|
||||
framework code may be in model (§8 / §14 Q-dos). *(documented.)*
|
||||
- **Already-disclosed S2-series vulnerabilities** — a duplicate of an existing
|
||||
Security Bulletin/CVE is closed by reference (the
|
||||
[`SECURITY.md` "Before Reporting"](SECURITY.md) checks), not re-triaged.
|
||||
- **Examples, showcase, and test applications** shipped in the repo. *(inferred — §14 Q-scope.)*
|
||||
|
||||
## §4 Trust boundaries and data flow
|
||||
|
||||
```
|
||||
Untrusted HTTP request
|
||||
│ params, headers, cookies, multipart
|
||||
▼
|
||||
Servlet filter ─► action mapping ─► Interceptor stack ─► Action ─► Result (JSP/FreeMarker)
|
||||
│ │
|
||||
ParametersInterceptor tag/result OGNL eval
|
||||
binds params to setters against ValueStack
|
||||
│ │
|
||||
▼ ▼
|
||||
OGNL evaluation against the ValueStack ◄── the trust boundary
|
||||
(allowlist / excluded classes+packages /
|
||||
expression length / @StrutsParameter)
|
||||
```
|
||||
|
||||
- **HTTP client → framework** is the one boundary Struts owns. Every request-derived
|
||||
string (parameter *names* as well as *values*, cookie names/values, header values,
|
||||
multipart filenames) is untrusted and may carry an OGNL payload. The framework's
|
||||
job at this boundary is to bind parameters and evaluate expressions **without
|
||||
letting attacker input reach an OGNL evaluation that creates or changes executable
|
||||
code**. *(documented.)*
|
||||
- **Framework → application code** is *not* a trust boundary — Struts runs the
|
||||
developer's actions and templates in-process, fully trusted. *(documented.)*
|
||||
|
||||
**Reachability precondition (triager's test).** A finding is in-model only if it is
|
||||
reachable by an **untrusted HTTP client against a Struts application that follows the
|
||||
documented secure configuration** (current-version defaults, `devMode` off, dev-only
|
||||
plugins restricted, no developer anti-patterns from §3). A finding that needs
|
||||
`devMode`, a disabled default protection, a developer-introduced unsafe setter, or a
|
||||
documented anti-pattern is `OUT-OF-MODEL`. *(documented/inferred — §14 Q-default.)*
|
||||
|
||||
## §5 Assumptions about the environment
|
||||
|
||||
- A servlet container and a JVM the operator maintains; Struts does not patch or
|
||||
harden them. *(inferred — §14 Q-env.)*
|
||||
- The application is deployed with the **current supported version** (7.x or 6.x per
|
||||
`SECURITY.md`); 2.x is end-of-life and out of support. *(documented — Supported
|
||||
Versions table.)*
|
||||
- The operator runs production with `devMode=false` and dev-only diagnostics (Config
|
||||
Browser Plugin) disabled or access-controlled. *(documented.)*
|
||||
- Struts opens no sockets and makes no outbound connections of its own; any network
|
||||
egress is the application's. *(inferred — §14 Q-egress.)*
|
||||
|
||||
## §5a Build-time and configuration variants — **the central knob set**
|
||||
|
||||
Struts' security envelope is set almost entirely by **runtime configuration**. The
|
||||
**authoritative, current list of every hardening setting (purpose + secure default)
|
||||
lives in the [security guidance](https://struts.apache.org/security/) and is not
|
||||
reproduced here.** Only the triage-load-bearing facts:
|
||||
|
||||
- The security posture **changed materially at 7.0**, where a cluster of
|
||||
OGNL-injection and parameter-binding defences became **secure-by-default** —
|
||||
notably the OGNL allowlist (`struts.allowlist.enable`), the `@StrutsParameter`
|
||||
annotation requirement (`struts.parameters.requireAnnotations`), excluded
|
||||
classes/packages, the expression-length cap (`struts.ognl.expressionMaxLength`,
|
||||
default 256), and the static-field/proxy/default-package/custom-map disallows.
|
||||
- `struts.devMode` (must be `false` in production) and Dynamic Method Invocation
|
||||
(gated by Strict Method Invocation since 2.5) are the two settings whose *insecure*
|
||||
value most often turns a non-finding into an apparent finding.
|
||||
- The **FetchMetadata / COOP / COEP** interceptors (6.0+) are opt-in cross-origin
|
||||
defences (§8.5).
|
||||
|
||||
**Insecure-default question (wave 1).** Because the secure posture is the **7.0
|
||||
default set**, the triage rule needs ratifying: is "a finding that only works with a
|
||||
pre-7.0 default, or with a 7.0 hardening knob turned off" `OUT-OF-MODEL:
|
||||
non-default-config`, with §10 carrying "deploy current version with defaults"? — §14
|
||||
Q-default. The OGNL **Java Security Manager sandbox** (`-Dognl.security.manager`) is a
|
||||
separate, opt-in defence built on the JDK `SecurityManager`, which has been
|
||||
**deprecated for removal since JDK 17 (JEP 411), disabled by default since JDK 18,
|
||||
and permanently disabled in JDK 24 (JEP 486)** *(documented — JDK release notes)* —
|
||||
so on modern JDKs the model cannot treat it as a relied-upon control (§14 Q-jsm).
|
||||
|
||||
## §6 Assumptions about inputs
|
||||
|
||||
| Surface | Input | Attacker-controllable? | Concern |
|
||||
| --- | --- | --- | --- |
|
||||
| Parameter binding | request parameter **names and values** | **yes** | OGNL injection via crafted names; binding to unsafe setters |
|
||||
| Cookies | cookie names/values (Cookie Interceptor) | **yes** | same OGNL/parameter concerns; checked by accepted/excluded patterns |
|
||||
| Headers | request headers | **yes** | header-driven expression/log paths |
|
||||
| Multipart upload | file content, filename, content-type | **yes** | parser robustness, temp-file handling (S2 history) |
|
||||
| Expression context | values that reach an OGNL eval (tags, results, forced eval) | **yes if developer feeds untrusted input in** | the core RCE channel |
|
||||
| struts.xml / annotations / action code | framework + app configuration | **no — developer-trusted** | not an attacker surface (§3) |
|
||||
|
||||
The accepted/excluded pattern checkers (`AcceptedPatternsChecker` /
|
||||
`ExcludedPatternsChecker`, since 2.3.20) validate parameter names/values for the
|
||||
Parameters and Cookie interceptors; a custom override that drops below the framework
|
||||
defaults is a developer error, not a framework flaw. *(documented.)*
|
||||
|
||||
## §7 Adversary model
|
||||
|
||||
- **In scope:** an **untrusted remote HTTP client** with no credentials, able to send
|
||||
arbitrary parameters, headers, cookies, and multipart uploads to any
|
||||
Struts-handled endpoint. Capabilities: craft parameter names/values carrying OGNL,
|
||||
attempt to reach executable-code creation through the ValueStack, pollute
|
||||
parameter binding, exploit a file-upload or multipart parsing bug, or trigger a
|
||||
super-linear resource path in framework code. Goal: **remote code execution via
|
||||
OGNL** (the dominant Struts threat), and secondarily data disclosure, SSRF through
|
||||
framework features, or DoS amplification. *(documented — the OGNL lineage is the
|
||||
framework's stated central concern.)*
|
||||
- **On-path network attacker** — only where the application/operator has not deployed
|
||||
TLS; transport security is the app's, so this is largely out of model (§3). *(inferred — §14 Q-env.)*
|
||||
- **Out of scope:** the application developer (writes trusted code/config); the
|
||||
operator (deploys, sets devMode/plugins); anyone with container/host/JVM control;
|
||||
and a developer who disables a default protection or follows a documented
|
||||
anti-pattern (§3). *(documented.)*
|
||||
|
||||
## §8 Security properties the framework provides
|
||||
|
||||
*(In the current-version, default-hardening posture; each lists violation symptom +
|
||||
severity. Most are documented controls — the OGNL-injection defences are the core of
|
||||
Struts' security work.)*
|
||||
|
||||
1. **OGNL injection containment.** Attacker-supplied request data (parameter names/
|
||||
values, cookies, headers) must not reach an OGNL evaluation that creates or alters
|
||||
executable code. Enforced in depth by the default controls listed in §5a / the
|
||||
[security guidance](https://struts.apache.org/security/) (allowlist, excluded
|
||||
classes/packages, expression-length cap, static-field/proxy/default-package/
|
||||
custom-map disallows, excluded node types). *Violation:* a crafted request
|
||||
achieving OGNL-driven code execution (or class-loader/member access beyond the
|
||||
allowlist) on a default-configured current-version app. *Severity:*
|
||||
security-critical (the S2-RCE class). *(documented.)*
|
||||
2. **Parameter-binding safety (7.0).** Request parameters bind only to setters the
|
||||
developer marked `@StrutsParameter` (to the declared depth); arbitrary deep/nested
|
||||
property traversal is not reachable by default. *Violation:* parameters reaching
|
||||
an unannotated setter, or nesting beyond the declared depth, on a default 7.0 app.
|
||||
*Severity:* critical. *(documented.)*
|
||||
3. **Method-invocation control.** Dynamic Method Invocation is gated by Strict Method
|
||||
Invocation; a client cannot invoke arbitrary action methods by name when DMI is at
|
||||
its recommended (off/strict) setting. *Violation:* arbitrary method invocation on a
|
||||
default app. *Severity:* high–critical. *(documented.)*
|
||||
4. **Expression-length and node-type bounds.** OGNL expressions over the configured
|
||||
length (default 256) and forbidden node types are rejected before evaluation.
|
||||
*Violation:* bypass of these bounds. *Severity:* high. *(documented.)*
|
||||
5. **Cross-origin / fetch-metadata defences (opt-in).** When the FetchMetadata, COOP,
|
||||
and COEP interceptors are enabled, the framework emits/enforces the corresponding
|
||||
`Sec-Fetch-*` and cross-origin isolation behaviour. *Violation:* the interceptor
|
||||
failing to enforce its documented behaviour when enabled. *Severity:* medium–high.
|
||||
*(documented — opt-in since 6.0.)*
|
||||
|
||||
## §9 Security properties the framework does *not* provide
|
||||
|
||||
- **No security mechanism in the general sense.** Struts provides no authentication,
|
||||
authorization, session security, CSRF token store, input sanitisation, or output
|
||||
encoding *for the application's own data* — "it is just a pure web framework."
|
||||
*(documented.)*
|
||||
- *False friend:* "Struts has no built-in login/access control" is **by design**,
|
||||
not a vulnerability.
|
||||
- **No protection against developer anti-patterns or non-default config** — unsafe
|
||||
setters, raw `${}` on user input, request params in localization/forced eval,
|
||||
direct JSP access, `devMode` on, disabled hardening (§3/§5a).
|
||||
- **No defence once OGNL evaluation is fed untrusted input by the application
|
||||
itself** (forced expression evaluation on a request value) — that is the developer
|
||||
handing OGNL the attacker's string. *(documented.)*
|
||||
- **No hard anti-DoS guarantee** beyond the "avoid super-linear in input size"
|
||||
philosophy; generic flooding/streaming DoS is the operator's to absorb. *(documented.)*
|
||||
- **The OGNL Java Security Manager sandbox is not a relied-upon control on modern
|
||||
JDKs** (the underlying `SecurityManager` is deprecated for removal since JDK 17 and
|
||||
permanently disabled in JDK 24; see §5a). *(documented.)*
|
||||
- **Auto-generated error pages do not escape action names** (historical S2-006) — the
|
||||
app must define custom error pages; XSS in the default error page is a documented
|
||||
hardening item, not a defended property. *(documented.)*
|
||||
- **Well-known classes (framework):** OGNL/expression injection, multipart/file-upload
|
||||
parsing bugs, and parameter-pollution are the framework's recurring risk classes;
|
||||
reflected XSS, CSRF token management, and transport security are the application's.
|
||||
|
||||
## §10 Downstream (developer + operator) responsibilities
|
||||
|
||||
The full, authoritative how-to is the [security guidance](https://struts.apache.org/security/)
|
||||
and [`SECURITY.md`](SECURITY.md); in one line: **deploy a current supported version
|
||||
with the default hardening left on, `devMode` off, dev-only plugins restricted,
|
||||
parameter setters annotated, JSPs hidden behind actions, and the application's own
|
||||
authn/authz/CSRF/TLS supplied** (Struts provides none of those). The threat-model
|
||||
value is only that a finding requiring the developer to *violate* one of these is
|
||||
`OUT-OF-MODEL` (§3/§13), not that this list is novel.
|
||||
|
||||
## §11 Known misuse patterns
|
||||
|
||||
These are the §3 application-responsibility / non-default-config items viewed as
|
||||
"things integrators get wrong" — running `devMode=true` in production or exposing the
|
||||
Config Browser Plugin; disabling a default OGNL/binding protection "to make something
|
||||
work"; exposing unsafe setters to binding; feeding request parameters into forced
|
||||
OGNL evaluation or localization; allowing direct `*.jsp` access or raw `${}` EL on
|
||||
untrusted values; relying on the OGNL Java Security Manager sandbox on modern JDKs. Each
|
||||
is documented in the [security guidance](https://struts.apache.org/security/); the
|
||||
disposition mapping is §11a/§13.
|
||||
|
||||
## §11a Known non-findings (recurring false positives)
|
||||
|
||||
*(Seeded directly from `SECURITY.md` "Before Reporting" — the PMC owns the
|
||||
authoritative list; §14 Q12.)*
|
||||
|
||||
- **"OGNL/RCE that only works with `devMode=true`."** `OUT-OF-MODEL: non-default-config`
|
||||
— devMode is a development-only setting documented as unsafe for production.
|
||||
- **"An action setter lets me inject a value / reach a dangerous method."** When the
|
||||
setter is developer-exposed without `@StrutsParameter` (7.0), or performs an unsafe
|
||||
side effect, this is `OUT-OF-MODEL: application-responsibility`. In-model only if it
|
||||
bypasses the framework's *default* binding/OGNL protections.
|
||||
- **"Direct JSP access discloses X / executes Y."** App-deployment misconfiguration —
|
||||
JSPs must be hidden behind actions. `OUT-OF-MODEL: application-responsibility`.
|
||||
- **"Raw `${}` EL / forced OGNL eval on my request parameter is exploitable."** The
|
||||
application fed untrusted input to expression evaluation — documented anti-pattern,
|
||||
not a framework flaw.
|
||||
- **"Config Browser Plugin exposes internal configuration."** Dev-only diagnostic;
|
||||
exposing it in production is operator misconfiguration. `OUT-OF-MODEL: non-default-config`.
|
||||
- **"I can enumerate / pass arbitrary parameters."** Parameter binding is the point of
|
||||
the framework; in-model only when it crosses the default annotation/allowlist
|
||||
protections.
|
||||
- **"Generic DoS: I streamed a huge body / hammered a URL."** Not accepted per
|
||||
`SECURITY.md`; only super-linear amplification inside framework code is considered.
|
||||
- **Duplicate of a disclosed S2-series bulletin/CVE** — closed by reference.
|
||||
- **Dependency-tail CVEs** (a transitive jar, e.g. a logging or XML library) from an
|
||||
SCA scan — triage upstream unless Struts' own code reaches the vulnerable path with
|
||||
untrusted input.
|
||||
|
||||
## §12 Conditions that would change this model
|
||||
|
||||
- A change to the default-hardening set (e.g. a new secure-by-default knob, or a
|
||||
default flipped) — re-baseline §5a/§8/§11a.
|
||||
- A new request-facing surface, a new bundled plugin, or a new expression/templating
|
||||
integration with its own trust surface.
|
||||
- A change to how OGNL evaluation, the allowlist, or parameter binding works.
|
||||
- A report that cannot be routed to a §13 disposition → revise §8/§9.
|
||||
|
||||
## §13 Triage dispositions
|
||||
|
||||
| Disposition | Meaning | Licensed by |
|
||||
| --- | --- | --- |
|
||||
| `VALID` | A §8 property breaks via an untrusted HTTP client on a current-version, default-hardened app. | §8, §6, §7 |
|
||||
| `VALID-HARDENING` | A §11 misuse is too easy, or a default could be tightened. | §11/§5a |
|
||||
| `OUT-OF-MODEL: application-responsibility` | Requires a developer anti-pattern (unsafe setter, raw EL, forced eval, direct JSP) or the app's own authn/authz. | §3/§10 |
|
||||
| `OUT-OF-MODEL: non-default-config` | Only manifests with `devMode`, a dev-only plugin, DMI, or a disabled default protection. | §5a |
|
||||
| `OUT-OF-MODEL: adversary-not-in-scope` | Requires container/host/JVM/developer control. | §7 |
|
||||
| `OUT-OF-MODEL: unsupported-version` | Only affects an end-of-life (2.x) version. | §5 |
|
||||
| `BY-DESIGN: property-disclaimed` | Concerns a property §9 disclaims (no built-in authn/authz/encoding; generic DoS; JSM on JDK21+). | §9 |
|
||||
| `KNOWN-NON-FINDING` | Matches §11a. | §11a |
|
||||
| `DUPLICATE` | Matches a disclosed S2-series bulletin/CVE. | §3 |
|
||||
| `MODEL-GAP` | Unroutable. | triggers §12 |
|
||||
|
||||
## §14 Open questions for the maintainers
|
||||
|
||||
**Wave 1 — scope, defaults, intended use**
|
||||
|
||||
- **Q-default.** Confirm the triage baseline is "current supported version (7.x/6.x)
|
||||
with the documented default hardening on, `devMode` off, dev-only plugins
|
||||
restricted" — and that a finding requiring a pre-7.0 default or a disabled hardening
|
||||
knob is `OUT-OF-MODEL: non-default-config`. (§5a/§13.)
|
||||
- **Q-scope.** Confirm the in-scope surface is the framework in `apache/struts`
|
||||
(core + interceptors + tags + bundled plugins), with the embedding application's own
|
||||
actions/JSPs/config, and examples/showcase, out of scope. (§2/§3.)
|
||||
- **Q-philosophy.** Confirm the framing that Struts provides **no security mechanism
|
||||
of its own** beyond OGNL/parameter-binding injection containment — i.e. authn,
|
||||
authz, session security, CSRF token storage, output encoding, and transport are the
|
||||
application's. (§9.)
|
||||
- **Q-env.** Confirm the servlet container, JVM, JDK, and OS are out of scope — Struts
|
||||
does not patch or harden them, and the operator maintains them. (§3/§5.)
|
||||
- **Q-egress.** Confirm Struts opens no sockets and makes no outbound connections of
|
||||
its own, so any network egress (and the SSRF surface it implies) is the
|
||||
application's. (§5/§7.)
|
||||
|
||||
**Wave 2 — mechanism confirmations**
|
||||
|
||||
- **Q-ognl.** Confirm the §8.1 list is the authoritative set of default OGNL-injection
|
||||
defences (allowlist, excluded classes/packages/patterns, expression length,
|
||||
static-field/proxy/default-package/custom-map disallows, excluded node types) and
|
||||
that a bypass of any on a default app is `VALID`. (§8.)
|
||||
- **Q-jsm.** Confirm the OGNL Java Security Manager sandbox is **not** a relied-upon
|
||||
control (opt-in, and non-functional on modern JDKs — see §5a), so a report premised
|
||||
on its absence is not a finding. (§5a/§9.)
|
||||
- **Q-dos.** Where is the line between "generic DoS we don't accept" and "super-linear
|
||||
amplification inside framework code we do"? Confirm the §3/§8 wording. (§3.)
|
||||
|
||||
**Wave 3 — surfaces & false-friends**
|
||||
|
||||
- **Q-plugins.** Which bundled plugins (REST, JSON, Convention, …) are in scope at the
|
||||
same request-trust level, and are any (e.g. REST/XML) historically higher-risk and
|
||||
worth their own §8 note? (§2.)
|
||||
- **Q-upload.** Confirm the multipart/file-upload surface (Jakarta) and what the
|
||||
framework guarantees vs. leaves to the container/app. (§2/§6.)
|
||||
- **Q12.** Beyond the `SECURITY.md` "Before Reporting" list already folded into §11a,
|
||||
what do scanners/researchers most often report against Struts that you consider a
|
||||
non-finding? (Feeds §11a.)
|
||||
|
||||
## §15 Appendix — existing-policy back-map
|
||||
|
||||
This `THREAT_MODEL.md` is **additive** — it does not replace
|
||||
[`SECURITY.md`](SECURITY.md) (reporting process, supported versions, "Before
|
||||
Reporting" checks) or the published [security guidance](https://struts.apache.org/security/);
|
||||
both are preserved and remain canonical for the reporting workflow. The discoverability
|
||||
chain is `AGENTS.md` → `SECURITY.md` → this model. Mapping of existing-policy claims to
|
||||
sections:
|
||||
|
||||
| Existing-policy statement | Threat-model § |
|
||||
| --- | --- |
|
||||
| "Struts doesn't provide any security mechanism — pure web framework" | §1, §9, §13 (`BY-DESIGN`) |
|
||||
| OGNL is the central historical vuln class | §1, §7, §8.1 |
|
||||
| devMode / Config Browser Plugin are dev-only | §3, §5a, §11a |
|
||||
| `@StrutsParameter` / unsafe setters | §6, §8.2, §10, §11a |
|
||||
| Direct JSP access / raw `${}` EL / forced eval / localization | §3, §10, §11a |
|
||||
| Allowlist / excluded classes/packages / expression length (7.0 defaults) | §5a, §8.1 |
|
||||
| DMI / Strict Method Invocation | §5a, §8.3 |
|
||||
| FetchMetadata / COOP / COEP | §5a, §8.5 |
|
||||
| OGNL JSM sandbox (modern-JDK limitation) | §5a, §9 |
|
||||
| Generic DoS not accepted; non-linear-in-input philosophy | §3, §8, §9 |
|
||||
| "Before Reporting" duplicate/known-config checks | §3, §11a, §13 (`DUPLICATE`) |
|
||||
| Supported versions (2.x EOL) | §5, §13 (`OUT-OF-MODEL: unsupported-version`) |
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>7.2.1-SNAPSHOT</version>
|
||||
<version>7.2.2-SNAPSHOT</version>
|
||||
<relativePath>../parent/pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>7.2.1-SNAPSHOT</version>
|
||||
<version>7.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>7.2.1-SNAPSHOT</version>
|
||||
<version>7.2.2-SNAPSHOT</version>
|
||||
<name>Struts 2 Rest Showcase Webapp</name>
|
||||
<description>Struts 2 Rest Showcase Example</description>
|
||||
|
||||
@@ -99,6 +99,14 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.npm</groupId>
|
||||
<artifactId>bootstrap-icons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<constant name="struts.convention.default.parent.package" value="rest-showcase"/>
|
||||
|
||||
<constant name="struts.convention.package.locators" value="example"/>
|
||||
<constant name="struts.webjars.allowlist" value="bootstrap,bootstrap-icons"/>
|
||||
|
||||
<!-- Uncomment the lines below to use Jackson XML bindings instead of the XStream library to handle XML serialisations -->
|
||||
<!--
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<title>Orders</title>
|
||||
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
|
||||
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
|
||||
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@@ -44,7 +45,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Delete Order ${id}</h1>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
</form>
|
||||
<br />
|
||||
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
|
||||
<i class="bi bi-arrow-left"></i> Back to Orders
|
||||
</a>
|
||||
</div><!--/col-md-12--->
|
||||
</div><!--/row-->
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<title>Orders</title>
|
||||
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
|
||||
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
|
||||
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@@ -44,22 +45,22 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Order <s:property value="id" /></h1>
|
||||
</div>
|
||||
|
||||
<s:actionmessage cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form method="post" action="%{#request.contextPath}/orders/%{id}" cssClass="form-horizontal" theme="simple">
|
||||
<s:form method="post" action="%{#request.contextPath}/orders/%{id}" theme="simple">
|
||||
<s:hidden name="_method" value="put" />
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="id">ID</label>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="id">ID</label>
|
||||
<div class="col-sm-4">
|
||||
<s:textfield id="id" name="id" disabled="true" cssClass="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="clientName">Client</label>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="clientName">Client</label>
|
||||
<div class="col-sm-4">
|
||||
<s:textfield id="clientName" name="clientName" cssClass="form-control"/>
|
||||
</div>
|
||||
@@ -67,8 +68,8 @@
|
||||
<s:fielderror fieldName="clientName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="amount">Amount</label>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="amount">Amount</label>
|
||||
<div class="col-sm-4">
|
||||
<s:textfield id="amount" name="amount" cssClass="form-control" />
|
||||
</div>
|
||||
@@ -76,15 +77,14 @@
|
||||
<s:fielderror fieldName="amount" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-4">
|
||||
<div class="row mb-3">
|
||||
<div class="offset-sm-2 col-sm-4">
|
||||
<s:submit cssClass="btn btn-primary"/>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
</s:form>
|
||||
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
|
||||
<i class="bi bi-arrow-left"></i> Back to Orders
|
||||
</a>
|
||||
</div><!--/col-md-12--->
|
||||
</div><!--/row-->
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<title>Orders</title>
|
||||
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
|
||||
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
|
||||
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@@ -44,15 +45,15 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>New Order</h1>
|
||||
</div>
|
||||
|
||||
<s:actionmessage cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form method="post" action="%{#request.contextPath}/orders" cssClass="form-horizontal" theme="simple">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="clientName">Client</label>
|
||||
<s:form method="post" action="%{#request.contextPath}/orders" theme="simple">
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="clientName">Client</label>
|
||||
<div class="col-sm-4">
|
||||
<s:textfield id="clientName" name="clientName" cssClass="form-control"/>
|
||||
</div>
|
||||
@@ -60,8 +61,8 @@
|
||||
<s:fielderror fieldName="clientName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="amount">Amount</label>
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-2 col-form-label" for="amount">Amount</label>
|
||||
<div class="col-sm-4">
|
||||
<s:textfield id="amount" name="amount" cssClass="form-control"/>
|
||||
</div>
|
||||
@@ -69,14 +70,14 @@
|
||||
<s:fielderror fieldName="amount" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-4">
|
||||
<div class="row mb-3">
|
||||
<div class="offset-sm-2 col-sm-4">
|
||||
<s:submit cssClass="btn btn-primary"/>
|
||||
</div>
|
||||
</div>
|
||||
</s:form>
|
||||
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-arrow-left"></apan> Back to Orders
|
||||
<i class="bi bi-arrow-left"></i> Back to Orders
|
||||
</a>
|
||||
</div><!--/col-md-12--->
|
||||
</div><!--/row-->
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<title>Orders</title>
|
||||
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
|
||||
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
|
||||
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@@ -44,7 +45,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Orders</h1>
|
||||
</div>
|
||||
<s:actionmessage cssClass="alert alert-danger"/>
|
||||
@@ -62,15 +63,15 @@
|
||||
<td><s:property value="amount"/></td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a href="orders/${id}" class="btn btn-default"><span class="glyphicon glyphicon-eye-open"></span> View</a>
|
||||
<a href="orders/${id}/edit" class="btn btn-default"><span class="glyphicon glyphicon-edit"></span> Edit</a>
|
||||
<a href="orders/${id}/deleteConfirm" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete</a>
|
||||
<a href="orders/${id}" class="btn btn-secondary"><i class="bi bi-eye"></i> View</a>
|
||||
<a href="orders/${id}/edit" class="btn btn-secondary"><i class="bi bi-pencil"></i> Edit</a>
|
||||
<a href="orders/${id}/deleteConfirm" class="btn btn-danger"><i class="bi bi-trash"></i> Delete</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</s:iterator>
|
||||
</table>
|
||||
<a href="orders/new" class="btn btn-primary"><span class="glyphicon glyphicon-file"></span> Create a new order</a>
|
||||
<a href="orders/new" class="btn btn-primary"><i class="bi bi-file-earmark"></i> Create a new order</a>
|
||||
</div><!--/col-md-12--->
|
||||
</div><!--/row-->
|
||||
</div><!--/container-->
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
<title>Orders</title>
|
||||
<!-- Using a standard HTML link tag with JSP EL to get the contextPath may be simpler, but this is an equivalent for s:link -->
|
||||
<s:set var="pageContextPath"><%=((HttpServletRequest)request).getContextPath()%></s:set>
|
||||
<s:link href="%{#pageContextPath}/css/bootstrap.min.css" rel="stylesheet"></s:link>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:link href="%{#pageContextPath}/css/app.css" rel="stylesheet"></s:link>
|
||||
|
||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
@@ -43,25 +44,25 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Order ${id}</h1>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td class="span3">ID</td>
|
||||
<td class="span9"><s:property value="id"/></td>
|
||||
<td class="col-3">ID</td>
|
||||
<td class="col-9"><s:property value="id"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="span3">Client</td>
|
||||
<td class="span9"><s:property value="clientName"/></td>
|
||||
<td class="col-3">Client</td>
|
||||
<td class="col-9"><s:property value="clientName"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="span3">Amount</td>
|
||||
<td class="span9"><s:property value="amount"/></td>
|
||||
<td class="col-3">Amount</td>
|
||||
<td class="col-9"><s:property value="amount"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a href="${pageContext.request.contextPath}/orders" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-arrow-left"></span> Back to Orders
|
||||
<i class="bi bi-arrow-left"></i> Back to Orders
|
||||
</a>
|
||||
</div><!--/col-md-12--->
|
||||
</div><!--/row-->
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap v3.3.4 (http://getbootstrap.com)
|
||||
* Copyright 2011-2015 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
.btn-default,
|
||||
.btn-primary,
|
||||
.btn-success,
|
||||
.btn-info,
|
||||
.btn-warning,
|
||||
.btn-danger {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-primary:active,
|
||||
.btn-success:active,
|
||||
.btn-info:active,
|
||||
.btn-warning:active,
|
||||
.btn-danger:active,
|
||||
.btn-default.active,
|
||||
.btn-primary.active,
|
||||
.btn-success.active,
|
||||
.btn-info.active,
|
||||
.btn-warning.active,
|
||||
.btn-danger.active {
|
||||
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
.btn-default .badge,
|
||||
.btn-primary .badge,
|
||||
.btn-success .badge,
|
||||
.btn-info .badge,
|
||||
.btn-warning .badge,
|
||||
.btn-danger .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.btn:active,
|
||||
.btn.active {
|
||||
background-image: none;
|
||||
}
|
||||
.btn-default {
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dbdbdb;
|
||||
border-color: #ccc;
|
||||
}
|
||||
.btn-default:hover,
|
||||
.btn-default:focus {
|
||||
background-color: #e0e0e0;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-default:active,
|
||||
.btn-default.active {
|
||||
background-color: #e0e0e0;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
.btn-default.disabled,
|
||||
.btn-default:disabled,
|
||||
.btn-default[disabled] {
|
||||
background-color: #e0e0e0;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-primary {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus {
|
||||
background-color: #265a88;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-primary:active,
|
||||
.btn-primary.active {
|
||||
background-color: #265a88;
|
||||
border-color: #245580;
|
||||
}
|
||||
.btn-primary.disabled,
|
||||
.btn-primary:disabled,
|
||||
.btn-primary[disabled] {
|
||||
background-color: #265a88;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success:hover,
|
||||
.btn-success:focus {
|
||||
background-color: #419641;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-success:active,
|
||||
.btn-success.active {
|
||||
background-color: #419641;
|
||||
border-color: #3e8f3e;
|
||||
}
|
||||
.btn-success.disabled,
|
||||
.btn-success:disabled,
|
||||
.btn-success[disabled] {
|
||||
background-color: #419641;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info:hover,
|
||||
.btn-info:focus {
|
||||
background-color: #2aabd2;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-info:active,
|
||||
.btn-info.active {
|
||||
background-color: #2aabd2;
|
||||
border-color: #28a4c9;
|
||||
}
|
||||
.btn-info.disabled,
|
||||
.btn-info:disabled,
|
||||
.btn-info[disabled] {
|
||||
background-color: #2aabd2;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning:hover,
|
||||
.btn-warning:focus {
|
||||
background-color: #eb9316;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-warning:active,
|
||||
.btn-warning.active {
|
||||
background-color: #eb9316;
|
||||
border-color: #e38d13;
|
||||
}
|
||||
.btn-warning.disabled,
|
||||
.btn-warning:disabled,
|
||||
.btn-warning[disabled] {
|
||||
background-color: #eb9316;
|
||||
background-image: none;
|
||||
}
|
||||
.btn-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger:hover,
|
||||
.btn-danger:focus {
|
||||
background-color: #c12e2a;
|
||||
background-position: 0 -15px;
|
||||
}
|
||||
.btn-danger:active,
|
||||
.btn-danger.active {
|
||||
background-color: #c12e2a;
|
||||
border-color: #b92c28;
|
||||
}
|
||||
.btn-danger.disabled,
|
||||
.btn-danger:disabled,
|
||||
.btn-danger[disabled] {
|
||||
background-color: #c12e2a;
|
||||
background-image: none;
|
||||
}
|
||||
.thumbnail,
|
||||
.img-thumbnail {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.dropdown-menu > li > a:hover,
|
||||
.dropdown-menu > li > a:focus {
|
||||
background-color: #e8e8e8;
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.dropdown-menu > .active > a,
|
||||
.dropdown-menu > .active > a:hover,
|
||||
.dropdown-menu > .active > a:focus {
|
||||
background-color: #2e6da4;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.navbar-default {
|
||||
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
|
||||
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-default .navbar-nav > .open > a,
|
||||
.navbar-default .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
|
||||
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.navbar-brand,
|
||||
.navbar-nav > li > a {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
|
||||
}
|
||||
.navbar-inverse {
|
||||
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
|
||||
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.navbar-inverse .navbar-nav > .open > a,
|
||||
.navbar-inverse .navbar-nav > .active > a {
|
||||
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
|
||||
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-inverse .navbar-brand,
|
||||
.navbar-inverse .navbar-nav > li > a {
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
.navbar-static-top,
|
||||
.navbar-fixed-top,
|
||||
.navbar-fixed-bottom {
|
||||
border-radius: 0;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
|
||||
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
|
||||
color: #fff;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
.alert {
|
||||
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
|
||||
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.alert-success {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #b2dba1;
|
||||
}
|
||||
.alert-info {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #9acfea;
|
||||
}
|
||||
.alert-warning {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #f5e79e;
|
||||
}
|
||||
.alert-danger {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dca7a7;
|
||||
}
|
||||
.progress {
|
||||
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-success {
|
||||
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
|
||||
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-info {
|
||||
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
|
||||
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-warning {
|
||||
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
|
||||
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-danger {
|
||||
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
|
||||
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.progress-bar-striped {
|
||||
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
|
||||
}
|
||||
.list-group {
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
|
||||
}
|
||||
.list-group-item.active,
|
||||
.list-group-item.active:hover,
|
||||
.list-group-item.active:focus {
|
||||
text-shadow: 0 -1px 0 #286090;
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #2b669a;
|
||||
}
|
||||
.list-group-item.active .badge,
|
||||
.list-group-item.active:hover .badge,
|
||||
.list-group-item.active:focus .badge {
|
||||
text-shadow: none;
|
||||
}
|
||||
.panel {
|
||||
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
||||
}
|
||||
.panel-default > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
|
||||
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-primary > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
|
||||
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-success > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
|
||||
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-info > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
|
||||
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-warning > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
|
||||
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.panel-danger > .panel-heading {
|
||||
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
|
||||
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
.well {
|
||||
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
|
||||
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #dcdcdc;
|
||||
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-theme.css.map */
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-6584
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15
-2
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>7.2.1-SNAPSHOT</version>
|
||||
<version>7.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
@@ -127,7 +127,7 @@
|
||||
<dependency>
|
||||
<groupId>org.sitemesh</groupId>
|
||||
<artifactId>sitemesh</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<version>3.2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -172,6 +172,19 @@
|
||||
<artifactId>groovy-jsr223</artifactId>
|
||||
<version>3.0.25</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>bootstrap</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars.npm</groupId>
|
||||
<artifactId>bootstrap-icons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webjars</groupId>
|
||||
<artifactId>jquery</artifactId>
|
||||
<version>${webjars-jquery-showcase.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
<Root level="info">
|
||||
<AppenderRef ref="STDOUT"/>
|
||||
</Root>
|
||||
<!--
|
||||
<Logger name="org.apache.struts2" level="info"/>
|
||||
<Logger name="org.apache.struts2.showcase.fileupload" level="debug"/>
|
||||
<Logger name="org.apache.struts2.inject" level="debug"/>
|
||||
<Logger name="org.apache.struts2.interceptor.ActionFileUploadInterceptor" level="debug"/>
|
||||
-->
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<constant name="struts.allowlist.enable" value="true"/>
|
||||
<constant name="struts.parameters.requireAnnotations" value="true"/>
|
||||
<constant name="struts.allowlist.packageNames" value="org.apache.struts2.showcase"/>
|
||||
<constant name="struts.webjars.allowlist" value="jquery,bootstrap,bootstrap-icons"/>
|
||||
|
||||
<!-- Enable Spring AOP proxy support for action chaining test (WW-5514) -->
|
||||
<constant name="struts.disallowProxyObjectAccess" value="false"/>
|
||||
@@ -53,7 +54,7 @@
|
||||
<constant name="struts.serve.static.browserCache" value="false"/>
|
||||
|
||||
<constant name="struts.action.excludePattern"
|
||||
value=".*/images/.*\.gif,.*/img/.*\.gif,.*/styles/.*\.css,.*/js/.*\.js,/testServlet/.*"/>
|
||||
value=".*/images/.*\.gif,.*/img/.*\.gif,/testServlet/.*"/>
|
||||
|
||||
<include file="struts-interactive.xml"/>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<title>Struts2 Showcase - Action Chaining Result</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Action Chaining Result:</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Bean Validation Examples</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Populate into Struts action class a Set of Address.java Object</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Populate into Struts action class a Set of Address.java Object</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Tiger 5 Enum</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Tiger 5 Enum</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Populate Object into Struts' action List</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Populate Object into Struts' action List</h1>
|
||||
</div>
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
|
||||
<p/>
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form action="submitPersonInfo" namespace="/conversion" method="post">
|
||||
<%--
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<title>Struts2 Showcase - Conversion</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Populate into Struts action class a Set of Address.java Object</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Populate into Struts action class a Set of Address.java Object</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Tiger 5 Enum</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Tiger 5 Enum</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Conversion - Populate Object into Struts' action List</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Conversion - Populate Object into Struts' action List</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -61,20 +61,26 @@
|
||||
|
||||
<title><sitemesh:write property="title"/></title>
|
||||
|
||||
<s:url var="bootstrapCss" value='/styles/bootstrap.css' encode='false' includeParams='none'/>
|
||||
<s:link href="%{bootstrapCss}" rel="stylesheet" type="text/css" media="all"/>
|
||||
<link rel="stylesheet" type="text/css" media="all" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<link rel="stylesheet" type="text/css" href="<s:webjar path='bootstrap-icons/font/bootstrap-icons.min.css'/>"/>
|
||||
<s:url var="mainCss" value='/styles/main.css' encode='false' includeParams='none'/>
|
||||
<s:link href="%{mainCss}" rel="stylesheet" type="text/css" media="all"/>
|
||||
|
||||
<s:url var="jqueryJs" value='/js/jquery-2.1.4.min.js' encode='false' includeParams='none'/>
|
||||
<s:script src="%{jqueryJs}"/>
|
||||
<s:url var="bootstrapJs" value='/js/bootstrap.min.js' encode='false' includeParams='none'/>
|
||||
<s:script src="%{bootstrapJs}"/>
|
||||
<script src="<s:webjar path='jquery/jquery.min.js'/>"></script>
|
||||
<script defer src="<s:webjar path='bootstrap/js/bootstrap.bundle.min.js'/>"></script>
|
||||
<s:script>
|
||||
$(function () {
|
||||
var alerts = $('ul.alert').wrap('<div />');
|
||||
alerts.prepend('<a class="close" data-dismiss="alert" href="#">×</a>');
|
||||
alerts.alert();
|
||||
$('ul.alert').each(function () {
|
||||
var ul = $(this);
|
||||
// Move the alert* classes (base + variant, e.g. alert-danger) onto a
|
||||
// dismissible wrapper so it becomes the single alert container, then
|
||||
// strip them from the <ul> to avoid a nested, uncoloured alert box.
|
||||
var alertClasses = (ul.attr('class').match(/\balert\S*/g) || []).join(' ');
|
||||
ul.removeClass(alertClasses);
|
||||
var wrapper = $('<div class="alert-dismissible" />').addClass(alertClasses);
|
||||
ul.before(wrapper);
|
||||
wrapper.append('<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>').append(ul);
|
||||
});
|
||||
});
|
||||
</s:script>
|
||||
|
||||
@@ -97,113 +103,107 @@
|
||||
|
||||
<body id="page-home">
|
||||
|
||||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
<nav class="navbar navbar-expand-lg bg-light fixed-top" data-bs-theme="light">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<s:url var="home" action="showcase" namespace="/" includeContext="false" />
|
||||
<s:a value="%{home}" cssClass="navbar-brand">
|
||||
Struts2 Showcase
|
||||
</s:a>
|
||||
</div>
|
||||
<s:url var="home" action="showcase" namespace="/" includeContext="false" />
|
||||
<s:a value="%{home}" cssClass="navbar-brand">
|
||||
Struts2 Showcase
|
||||
</s:a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-main" aria-controls="navbar-main" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<div class="collapse navbar-collapse" id="navbar-main">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><s:a value="%{home}"><i class="glyphicon glyphicon-home"></i> Home</s:a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="glyphicon glyphicon-cog"></i> Configuration
|
||||
<b class="caret"></b></a>
|
||||
<li class="nav-item"><s:a value="%{home}" cssClass="nav-link"><i class="bi bi-house"></i> Home</s:a></li>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-gear"></i> Configuration</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><s:a action="actionChain1!input" namespace="/actionchaining"
|
||||
includeParams="none">Action Chaining</s:a></li>
|
||||
includeParams="none" cssClass="dropdown-item">Action Chaining</s:a></li>
|
||||
<li><s:a action="index" namespace="/config-browser"
|
||||
includeParams="none">Config Browser</s:a></li>
|
||||
includeParams="none" cssClass="dropdown-item">Config Browser</s:a></li>
|
||||
<s:url var="conversion" action="index" namespace="/conversion" includeContext="false" />
|
||||
<li><s:a value="%{conversion}">Conversion</s:a></li>
|
||||
<li><s:a value="/person/index.html">Person Manager ( by Conventions )</s:a></li>
|
||||
<li><s:a value="%{conversion}" cssClass="dropdown-item">Conversion</s:a></li>
|
||||
<li><s:a value="/person/index.html" cssClass="dropdown-item">Person Manager ( by Conventions )</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Non UI Tags<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Non UI Tags</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><s:url var="url" action="showActionTagDemo" namespace="/tags/non-ui/actionTag"/>
|
||||
<s:a href="%{url}">Action Tag</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Action Tag</s:a></li>
|
||||
<li><s:url var="url" namespace="/tags/non-ui" action="date"/>
|
||||
<s:a href="%{url}">Date Tag</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Date Tag</s:a></li>
|
||||
<li><s:url var="url" action="debugTagDemo" namespace="/tags/non-ui"/>
|
||||
<s:a href="%{url}">Debug Tag</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Debug Tag</s:a></li>
|
||||
<li><s:url var="url" action="showGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag"/>
|
||||
<s:a href="%{url}">Iterator Generator Tag</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Iterator Generator Tag</s:a></li>
|
||||
<li>
|
||||
<s:url var="url" action="showAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag"/>
|
||||
<s:a href="%{#url}">Append Iterator Tag</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Append Iterator Tag</s:a>
|
||||
<li>
|
||||
<s:url var="url" action="showMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag"/>
|
||||
<s:a href="%{#url}">Merge Iterator Demo</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Merge Iterator Demo</s:a>
|
||||
<li>
|
||||
<s:url var="url" action="showSubsetTagDemo" namespace="/tags/non-ui/subsetIteratorTag"/>
|
||||
<s:a href="%{#url}">Subset Tag</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Subset Tag</s:a>
|
||||
<li><s:url var="url" action="actionPrefixExampleUsingFreemarker" namespace="/tags/non-ui/actionPrefix"/>
|
||||
<s:a href="%{#url}">Action Prefix Example (Freemarker)</s:a></li>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Action Prefix Example (Freemarker)</s:a></li>
|
||||
<li><s:url var="url" action="testIfTagJsp" namespace="/tags/non-ui/ifTag"/>
|
||||
<s:a href="%{#url}">If Tag (JSP)</s:a></li>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">If Tag (JSP)</s:a></li>
|
||||
<li><s:url var="url" action="testIfTagFreemarker" namespace="/tags/non-ui/ifTag"/>
|
||||
<s:a href="%{#url}">If Tag (Freemarker)</s:a></li>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">If Tag (Freemarker)</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">UI Tags<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">UI Tags</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><s:url var="url" namespace="/tags/ui" action="example" method="input"/>
|
||||
<s:a href="%{url}">UI Example</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">UI Example</s:a></li>
|
||||
<li><s:url var="url" namespace="/tags/ui" action="exampleVelocity" method="input"/>
|
||||
<s:a href="%{url}">UI Example (Velocity)</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">UI Example (Velocity)</s:a></li>
|
||||
<li><s:url var="url" namespace="/tags/ui" action="lotsOfOptiontransferselect" method="input"/>
|
||||
<s:a href="%{url}">Option Transfer Select UI Example</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Option Transfer Select UI Example</s:a></li>
|
||||
<li><s:url var="url" namespace="/tags/ui" action="moreSelects" method="input"/>
|
||||
<s:a href="%{url}">More Select Box UI Examples</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">More Select Box UI Examples</s:a></li>
|
||||
<li>
|
||||
<s:url var="url" namespace="/tags/ui" action="componentTagExample"/>
|
||||
<s:a href="%{#url}">Component Tag Example</s:a></li>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Component Tag Example</s:a></li>
|
||||
<li><s:url var="url" namespace="/tags/ui" action="actionTagExample" method="input"/>
|
||||
<s:a href="%{url}">Action Tag Example</s:a></li>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Action Tag Example</s:a></li>
|
||||
<li><s:url var="url" action="index" namespace="/html5"/>
|
||||
<s:a href="%{#url}">Html 5 theme</s:a></li>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Html 5 theme</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="glyphicon glyphicon-file"></i> File
|
||||
<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-file-earmark"></i> File</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><s:a namespace="/filedownload" action="index">File Download</s:a></li>
|
||||
<li><s:a namespace="/filedownload" action="index" cssClass="dropdown-item">File Download</s:a></li>
|
||||
<li>
|
||||
<s:url var="url" action="upload" namespace="/fileupload"/>
|
||||
<s:a href="%{#url}">Single File Upload</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Single File Upload</s:a>
|
||||
</li>
|
||||
<li>
|
||||
<s:url var="url" action="dynamicUpload" namespace="/fileupload"/>
|
||||
<s:a href="%{#url}">Single File Upload - dynamic config</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Single File Upload - dynamic config</s:a>
|
||||
</li>
|
||||
<li>
|
||||
<s:url var="url" action="multipleUploadUsingList" namespace="/fileupload"/>
|
||||
<s:a href="%{#url}">Multiple File Upload (List)</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Multiple File Upload (List)</s:a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<s:url var="url" action="multipleUploadUsingArray" namespace="/fileupload"/>
|
||||
<s:a href="%{#url}">Multiple File Upload (Array)</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Multiple File Upload (Array)</s:a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Validation<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Validation</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<s:url var="quizBasic" namespace="/validation" action="quizBasic" method="input"/>
|
||||
<s:url var="quizClient" namespace="/validation" action="quizClient" method="input"/>
|
||||
@@ -216,65 +216,64 @@
|
||||
<s:url var="storeMessageAcrossRequestExample" namespace="/validation" action="storeErrorsAcrossRequestExample"/>
|
||||
<s:url var="beanValidationUrl" action="bean-validation" namespace="/bean-validation"/>
|
||||
<s:url var="ajaxFormSubmitUrl" action="ajaxFormSubmit" namespace="/validation" method="input"/>
|
||||
<li><s:a href="%{beanValidationUrl}">Bean Validation</s:a></li>
|
||||
<li><s:a href="%{fieldValidatorUrl}">Field Validators</s:a></li>
|
||||
<li><s:a href="%{clientSideValidationUrl}">Field Validators with client-side JavaScript</s:a></li>
|
||||
<li><s:a href="%{nonFieldValidatorUrl}">Non Field Validator</s:a></li>
|
||||
<li><s:a href="%{storeMessageAcrossRequestExample}">Store across request using MessageStoreInterceptor (Example)</s:a></li>
|
||||
<li><s:a href="%{quizBasic}">Validation (basic)</s:a></li>
|
||||
<li><s:a href="%{quizClient}">Validation (client)</s:a></li>
|
||||
<li><s:a href="%{quizClientCss}">Validation (client using css_xhtml theme)</s:a></li>
|
||||
<li><s:a href="%{visitorValidatorUrl}">Visitor Validator</s:a></li>
|
||||
<li><s:a href="%{ajaxFormSubmitUrl}">AJAX Form Submit</s:a></li>
|
||||
<li><s:a href="%{beanValidationUrl}" cssClass="dropdown-item">Bean Validation</s:a></li>
|
||||
<li><s:a href="%{fieldValidatorUrl}" cssClass="dropdown-item">Field Validators</s:a></li>
|
||||
<li><s:a href="%{clientSideValidationUrl}" cssClass="dropdown-item">Field Validators with client-side JavaScript</s:a></li>
|
||||
<li><s:a href="%{nonFieldValidatorUrl}" cssClass="dropdown-item">Non Field Validator</s:a></li>
|
||||
<li><s:a href="%{storeMessageAcrossRequestExample}" cssClass="dropdown-item">Store across request using MessageStoreInterceptor (Example)</s:a></li>
|
||||
<li><s:a href="%{quizBasic}" cssClass="dropdown-item">Validation (basic)</s:a></li>
|
||||
<li><s:a href="%{quizClient}" cssClass="dropdown-item">Validation (client)</s:a></li>
|
||||
<li><s:a href="%{quizClientCss}" cssClass="dropdown-item">Validation (client using css_xhtml theme)</s:a></li>
|
||||
<li><s:a href="%{visitorValidatorUrl}" cssClass="dropdown-item">Visitor Validator</s:a></li>
|
||||
<li><s:a href="%{ajaxFormSubmitUrl}" cssClass="dropdown-item">AJAX Form Submit</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Examples<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Examples</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li class="dropdown-submenu">
|
||||
<li>
|
||||
<s:url var="url" namespace="/hangman" action="hangmanNonAjax"/>
|
||||
<s:a href="%{url}">Hangman</s:a>
|
||||
<s:a href="%{url}" cssClass="dropdown-item">Hangman</s:a>
|
||||
</li>
|
||||
<li><s:a value="/person/index.html">Person Manager</s:a></li>
|
||||
<li><s:a value="/skill/index.html">CRUD</s:a></li>
|
||||
<li><s:a value="/wait/index">Execute & Wait</s:a></li>
|
||||
<li><s:a value="/token/index.html">Token</s:a></li>
|
||||
<li><s:url var="url" namespace="/modelDriven" action="modelDriven"/><s:a
|
||||
<li><s:a value="/person/index.html" cssClass="dropdown-item">Person Manager</s:a></li>
|
||||
<li><s:a value="/skill/index.html" cssClass="dropdown-item">CRUD</s:a></li>
|
||||
<li><s:a value="/wait/index" cssClass="dropdown-item">Execute & Wait</s:a></li>
|
||||
<li><s:a value="/token/index.html" cssClass="dropdown-item">Token</s:a></li>
|
||||
<li><s:url var="url" namespace="/modelDriven" action="modelDriven"/><s:a cssClass="dropdown-item"
|
||||
href="%{url}">Model Driven</s:a></li>
|
||||
<li><s:a value="/async/index.html">Async</s:a></li>
|
||||
<li><s:a value="/dispatcher/dispatch.action">Dispatcher result - dispatch</s:a></li>
|
||||
<li><s:a value="/dispatcher/forward.action">Dispatcher result - forward</s:a></li>
|
||||
<li><s:a value="/async/index.html" cssClass="dropdown-item">Async</s:a></li>
|
||||
<li><s:a value="/dispatcher/dispatch.action" cssClass="dropdown-item">Dispatcher result - dispatch</s:a></li>
|
||||
<li><s:a value="/dispatcher/forward.action" cssClass="dropdown-item">Dispatcher result - forward</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Integration<b class="caret"></b></a>
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">Integration</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<s:url var="url" action="customFreemarkerManagerDemo" namespace="/freemarker"/>
|
||||
<s:a href="%{#url}">Demo of usage of a Custom Freemarker Manager</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Demo of usage of a Custom Freemarker Manager</s:a>
|
||||
</li>
|
||||
<li>
|
||||
<s:url var="url" action="standardTags" namespace="/freemarker"/>
|
||||
<s:a href="%{#url}">Demo of Standard Struts Freemarker Tags</s:a>
|
||||
<s:a href="%{#url}" cssClass="dropdown-item">Demo of Standard Struts Freemarker Tags</s:a>
|
||||
</li>
|
||||
<li><s:a value="/tiles/index.action">Tiles</s:a></li>
|
||||
<li><s:a value="/tiles/index.action" cssClass="dropdown-item">Tiles</s:a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav pull-right">
|
||||
<li class="dropdown last">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="glyphicon glyphicon-question-sign"></i> Help<b class="caret"></b></a>
|
||||
<ul class="nav navbar-nav ms-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-question-circle"></i> Help</a>
|
||||
<ul class="dropdown-menu">
|
||||
<s:url var="help" action="help" namespace="/" includeContext="false" />
|
||||
<li><s:a value="%{help}">Help</s:a></li>
|
||||
<li><a href="http://struts.apache.org/mail.html"><i class="icon-share"></i> User Mailing
|
||||
<li><s:a value="%{help}" cssClass="dropdown-item">Help</s:a></li>
|
||||
<li><a href="http://struts.apache.org/mail.html" class="dropdown-item"><i class="bi bi-share"></i> User Mailing
|
||||
List</a></li>
|
||||
<li><a href="http://struts.apache.org"><i class="icon-share"></i> Struts2 Website</a>
|
||||
<li><a href="http://struts.apache.org" class="dropdown-item"><i class="bi bi-share"></i> Struts2 Website</a>
|
||||
</li>
|
||||
<li><a href="http://struts.apache.org/docs/home.html"><i class="icon-share"></i>
|
||||
<li><a href="http://struts.apache.org/docs/home.html" class="dropdown-item"><i class="bi bi-share"></i>
|
||||
Documentation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -295,7 +294,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pull-right">
|
||||
<div class="float-end">
|
||||
<div>
|
||||
<s:action var="dateAction" name="date" namespace="/" executeResult="true"/>
|
||||
</div>
|
||||
@@ -310,7 +309,7 @@
|
||||
<!-- end search -->
|
||||
</div>
|
||||
|
||||
<div class="pull-left">
|
||||
<div class="float-start">
|
||||
Copyright © 2003-<s:property value="#dateAction.now.year + 1900"/>
|
||||
<a href="https://www.apache.org">The Apache Software Foundation.</a>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Dispatcher Result Example</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1><s:property value="#title"/></h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<title>Struts2 Showcase - CRUD Example - <s:property value="#title"/></title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1><s:property value="#title"/></h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - CRUD Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Available Employees</h1>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>First Name</th>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - CRUD Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Available Skills</h1>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<tr>
|
||||
<th>Name</th><th>Description</th>
|
||||
</tr>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>File Download Example</h1>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<s:url var="url" action="download"/>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-picture"></i> Download image file.</s:a>
|
||||
<s:a href="%{url}" cssClass="btn btn-lg btn-info"><i class="bi bi-image"></i> Download image file.</s:a>
|
||||
</div>
|
||||
<div class="col-md-6" style="text-align: center;">
|
||||
<div class="alert alert-info">
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
<s:url var="url" action="download2"/>
|
||||
<s:a href="%{url}" cssClass="btn btn-large btn-info"><i class="icon-download-alt"></i> Download ZIP file.</s:a>
|
||||
<s:a href="%{url}" cssClass="btn btn-lg btn-info"><i class="bi bi-download"></i> Download ZIP file.</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>File Upload Successful</h1>
|
||||
<p class="lead">Your file was validated and uploaded successfully</p>
|
||||
</div>
|
||||
@@ -42,50 +42,50 @@
|
||||
<strong>Success!</strong> Your file passed all validation checks.
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Upload Details</h3>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Upload Details</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Upload Type:</dt>
|
||||
<dd><s:property value="uploadType == 'image' ? 'Image' : 'Document'"/></dd>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">Upload Type:</dt>
|
||||
<dd class="col-sm-9"><s:property value="uploadType == 'image' ? 'Image' : 'Document'"/></dd>
|
||||
|
||||
<dt>Content Type:</dt>
|
||||
<dd><code><s:property value="contentType"/></code></dd>
|
||||
<dt class="col-sm-3">Content Type:</dt>
|
||||
<dd class="col-sm-9"><code><s:property value="contentType"/></code></dd>
|
||||
|
||||
<dt>File Name:</dt>
|
||||
<dd><s:property value="fileName"/></dd>
|
||||
<dt class="col-sm-3">File Name:</dt>
|
||||
<dd class="col-sm-9"><s:property value="fileName"/></dd>
|
||||
|
||||
<dt>Original Name:</dt>
|
||||
<dd><s:property value="originalName"/></dd>
|
||||
<dt class="col-sm-3">Original Name:</dt>
|
||||
<dd class="col-sm-9"><s:property value="originalName"/></dd>
|
||||
|
||||
<dt>File Size:</dt>
|
||||
<dd><s:property value="uploadSize"/> bytes</dd>
|
||||
<dt class="col-sm-3">File Size:</dt>
|
||||
<dd class="col-sm-9"><s:property value="uploadSize"/> bytes</dd>
|
||||
|
||||
<dt>Input Name:</dt>
|
||||
<dd><s:property value="inputName"/></dd>
|
||||
<dt class="col-sm-3">Input Name:</dt>
|
||||
<dd class="col-sm-9"><s:property value="inputName"/></dd>
|
||||
|
||||
<dt>File Object:</dt>
|
||||
<dd><code><s:property value="uploadedFile"/></code></dd>
|
||||
<dt class="col-sm-3">File Object:</dt>
|
||||
<dd class="col-sm-9"><code><s:property value="uploadedFile"/></code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Validation Rules Applied</h3>
|
||||
<div class="card border-info">
|
||||
<div class="card-header text-bg-info">
|
||||
<h3 class="card-title">Validation Rules Applied</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>Allowed MIME Types:</dt>
|
||||
<dd><code><s:property value="uploadConfig.allowedMimeTypes"/></code></dd>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">Allowed MIME Types:</dt>
|
||||
<dd class="col-sm-9"><code><s:property value="uploadConfig.allowedMimeTypes"/></code></dd>
|
||||
|
||||
<dt>Allowed Extensions:</dt>
|
||||
<dd><code><s:property value="uploadConfig.allowedExtensions"/></code></dd>
|
||||
<dt class="col-sm-3">Allowed Extensions:</dt>
|
||||
<dd class="col-sm-9"><code><s:property value="uploadConfig.allowedExtensions"/></code></dd>
|
||||
|
||||
<dt>Maximum Size:</dt>
|
||||
<dd><s:property value="uploadConfig.maxFileSizeFormatted"/></dd>
|
||||
<dt class="col-sm-3">Maximum Size:</dt>
|
||||
<dd class="col-sm-9"><s:property value="uploadConfig.maxFileSizeFormatted"/></dd>
|
||||
</dl>
|
||||
<p class="text-muted">
|
||||
<small>
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
<div class="btn-group">
|
||||
<s:a action="dynamicUpload" cssClass="btn btn-primary">
|
||||
<i class="glyphicon glyphicon-upload"></i> Upload Another File
|
||||
<i class="bi bi-upload"></i> Upload Another File
|
||||
</s:a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Dynamic File Upload Validation</h1>
|
||||
<p class="lead">Demonstrates WithLazyParams for runtime validation rules</p>
|
||||
</div>
|
||||
@@ -73,14 +73,14 @@
|
||||
<s:file name="upload" label="Select File" cssClass="form-control"/>
|
||||
|
||||
<s:submit value="Upload File" cssClass="btn btn-primary"/>
|
||||
<s:submit value="Refresh Rules" action="dynamicUpload" cssClass="btn btn-default"/>
|
||||
<s:submit value="Refresh Rules" action="dynamicUpload" cssClass="btn btn-secondary"/>
|
||||
</s:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="well">
|
||||
<div class="bg-light border rounded p-3">
|
||||
<h4>How It Works</h4>
|
||||
<p>In <code>struts.xml</code>, the interceptor parameters use expressions:</p>
|
||||
<pre><interceptor-ref name="actionFileUpload">
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Fileupload sample - Multiple fileupload</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Fileupload sample - Multiple fileupload using Array</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Fileupload sample - Multiple fileupload using List</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Fileupload sample</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Fileupload sample</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
<s:form action="doUpload" method="POST" enctype="multipart/form-data">
|
||||
<s:file name="upload" label="File"/>
|
||||
<s:textfield name="caption" label="Caption"/>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Custom Freemarker Manager Usage</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Standard Struts Freemarker Tags</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Hangman</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Hangman</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Getting support</h1>
|
||||
</div>
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
<div class="alert alert-info">
|
||||
Use this mailing list if you encounter problems while developing and using with Struts.
|
||||
</div>
|
||||
<a href="http://struts.apache.org/mail.html" class="btn btn-large btn-info"><i class="glyphicon glyphicon-share"></i> User List</a>
|
||||
<a href="http://struts.apache.org/mail.html" class="btn btn-lg btn-info"><i class="bi bi-share"></i> User List</a>
|
||||
</div>
|
||||
<div class="col-md-4" style="text-align: center;">
|
||||
<div class="alert alert-info">
|
||||
The Struts 2 website.
|
||||
</div>
|
||||
<a href="http://struts.apache.org" class="btn btn-large btn-info"><i class="glyphicon glyphicon-share"></i> Struts 2</a>
|
||||
<a href="http://struts.apache.org" class="btn btn-lg btn-info"><i class="bi bi-share"></i> Struts 2</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
<s:compress>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<s:url var="bootstrapCss" value="/styles/bootstrap.css" encode="false" includeParams="none"/>
|
||||
<s:link theme="html5" href="%{bootstrapCss}"/>
|
||||
<link rel="stylesheet" href="<s:webjar path='bootstrap/css/bootstrap.min.css'/>"/>
|
||||
<s:url var="mainCss" value="/styles/main.css" encode="false" includeParams="none"/>
|
||||
<s:link theme="html5" href="%{mainCss}" />
|
||||
<s:head theme="html5"/>
|
||||
@@ -35,7 +34,7 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="hero-unit">
|
||||
<div class="p-5 mb-4 bg-light rounded-3">
|
||||
<h1>Html 5 tags demo</h1>
|
||||
<p>All the tags on this page are from <i>html5</i> theme. <s:a theme="html5" action="showcase" namespace="/">Back</s:a> to main Showcase App page</p>
|
||||
</div>
|
||||
@@ -45,7 +44,7 @@
|
||||
<!-- Section 1: Link Components -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h2>Link Components</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,7 +61,7 @@
|
||||
<!-- Section 2: Error & Message Components -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h2>Error & Message Components</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +94,7 @@
|
||||
<!-- Section 3: Form Components -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h2>Form Components</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -212,7 +211,7 @@
|
||||
<!-- Section 4: Advanced Selection Components -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h2>Advanced Selection Components</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,7 +228,7 @@
|
||||
<!-- Section 5: Utility & Display Components -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h2>Utility & Display Components</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Model Driven Example</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Model Driven Example - Result</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<title>Struts2 Showcase - Person Manager Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Person Manager Example</h1>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="col-md-9">
|
||||
<s:form action="edit-person" theme="simple" validate="false">
|
||||
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>First Name</th>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Person Manager Example - All People</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>All People</h1>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<p>There are ${peopleCount} people...</p>
|
||||
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Person Manager Example - New Person</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>New Person</h1>
|
||||
</div>
|
||||
|
||||
@@ -40,25 +40,21 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<@s.actionerror cssClass="alert alert-error"/>
|
||||
<@s.actionerror cssClass="alert alert-danger"/>
|
||||
<@s.actionmessage cssClass="alert alert-info"/>
|
||||
<@s.fielderror cssClass="alert alert-error"/>
|
||||
<@s.fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<@s.form action="new-person" theme="simple" cssClass="form-horizontal">
|
||||
<@s.form action="new-person" theme="simple">
|
||||
<legend>Create a new Person</legend>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="name">First Name<span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<@s.textfield id="name" name="person.name" placeholder="First Name"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="name">First Name<span class="required">*</span></label>
|
||||
<@s.textfield id="name" name="person.name" placeholder="First Name" cssClass="form-control"/>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="lastName">Last Name<span class="required">*</span></label>
|
||||
<div class="controls">
|
||||
<@s.textfield id="lastName" name="person.lastName" placeholder="Last Name"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="lastName">Last Name<span class="required">*</span></label>
|
||||
<@s.textfield id="lastName" name="person.lastName" placeholder="Last Name" cssClass="form-control"/>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<div class="mb-3">
|
||||
<@s.submit value="Create person" cssClass="btn btn-primary"/>
|
||||
</div>
|
||||
</@s.form>
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="hero-unit">
|
||||
<div class="p-5 mb-4 bg-light rounded-3">
|
||||
<h1>Welcome!</h1>
|
||||
<p>The Struts Showcase demonstrates a variety of use cases and tag usages. Essentially, the application exercises various framework features in isolation. The Showcase is not meant as a "best practices" example.</p>
|
||||
<p>For more "by example" solutions, see the <a href="https://github.com/apache/struts-examples" class="btn btn-primary btn-large">Struts Examples »</a> pages.</p>
|
||||
<p>For more "by example" solutions, see the <a href="https://github.com/apache/struts-examples" class="btn btn-primary btn-lg">Struts Examples »</a> pages.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<p>The text you've entered is ${text!''}<p/>
|
||||
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<p>The text you've enter is ${text!''}<p/>
|
||||
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<p>The text you've enter is %{text}<p/>
|
||||
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@
|
||||
<title>Struts2 Showcase - Non UI Tags - Action Prefix (Freemarker)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Action Prefix (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
The text you've enter is ${text!''}<p/>
|
||||
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
|
||||
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back</@s.a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non-Ui Tag - Action Tag</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<div class="well">
|
||||
<div class="bg-light border rounded p-3">
|
||||
<h2> This is Not - Included by the Action Tag</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- lets include the first page many times -->
|
||||
<div class="well">
|
||||
<div class="bg-light border rounded p-3">
|
||||
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
|
||||
<!-- lets include the second page many times -->
|
||||
<div class="well">
|
||||
<div class="bg-light border rounded p-3">
|
||||
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage2" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
|
||||
<!-- lets include the third page many time -->
|
||||
<div class="well">
|
||||
<div class="bg-light border rounded p-3">
|
||||
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
<s:action name="includePage3" namespace="/tags/non-ui/actionTag" executeResult="true" />
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non UI Tags Example - Date</h1>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<s:action var="myDate" name="date" namespace="/" executeResult="false" />
|
||||
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Debug Tag Usage</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Test If Tag (Freemarker)</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Test If Tag</h1>
|
||||
</div>
|
||||
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - AppendIterator Tag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</s:iterator>
|
||||
|
||||
<s:url var="url" action="showAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Iterator Generator Tag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
|
||||
<s:url var="url" action="showGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - MergeIterator Tag</h1>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</s:iterator>
|
||||
|
||||
<s:url var="url" action="showMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - AppendIterator Tag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form action="submitAppendTagDemo" namespace="/tags/non-ui/appendIteratorTag" method="POST">
|
||||
<s:textfield label="iterator 1 values (comma separated)" name="iteratorValue1" />
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - Iterator Generator Tag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form action="submitGeneratorTagDemo" namespace="/tags/non-ui/iteratorGeneratorTag" method="POST">
|
||||
<s:textfield label="Value" name="value" />
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - MergeIterator Tag</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form action="submitMergeTagDemo" namespace="/tags/non-ui/mergeIteratorTag" method="POST">
|
||||
<s:textfield label="Iterator 1 Value (Comma Separated)" name="iteratorValue1" />
|
||||
|
||||
+3
-3
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - SubsetTag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
<s:form action="submitSubsetTagDemo" namespace="/tags/non-ui/subsetIteratorTag" method="POST">
|
||||
<s:textfield label="Iterator value (comma separated)" name="iteratorValue" />
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Non Ui Tag - SubsetTag Demo</h1>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</s:subset>
|
||||
|
||||
<s:url var="url" action="showSubsetTagDemo" namespace="/tags/non-ui/subsetIteratorTag" />
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back To Input</s:a>
|
||||
<s:a href="%{#url}" cssClass="btn btn-info"><i class="bi bi-arrow-left"></i> Back To Input</s:a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - Action Tag</h1>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="col-md-12">
|
||||
<b>Example 1:</b>
|
||||
This example calls an action and includes the output on the page
|
||||
<p id="example1" class="well">
|
||||
<p id="example1" class="bg-light border rounded p-3">
|
||||
<s:action namespace="/tags/ui" name="actionTagExample" executeResult="true"/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - Component Tag</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags Example</h1>
|
||||
</div>
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
|
||||
<s:actionerror cssClass="alert alert-error"/>
|
||||
<s:actionerror cssClass="alert alert-danger"/>
|
||||
<s:actionmessage cssClass="alert alert-info"/>
|
||||
<s:fielderror cssClass="alert alert-error"/>
|
||||
<s:fielderror cssClass="alert alert-danger"/>
|
||||
|
||||
|
||||
<s:form action="exampleSubmit" enctype="multipart/form-data" method="POST" javascriptTooltip="true">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#shead()
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags Example (Velocity)</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags Example - Example Submited</h1>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped table-bordered table-hover table-condensed">
|
||||
<table class="table table-striped table-bordered table-hover table-sm">
|
||||
<s:label label="Name" name="name" />
|
||||
<s:label label="Birthday" name="birthday" />
|
||||
<tr>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<title>Struts2 Showcase - UI Tags Example (Velocity) - Example Submited </title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags Example (Velocity) - Example Submited</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - Optiontransferselect</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - Optiontransferselect Result</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - More Select Box UI Examples</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<s:head/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>UI Tags - More Select Box UI Examples - Result</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@
|
||||
* under the License.
|
||||
*/
|
||||
-->
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>${title}</h1>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Double post</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Example 1</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Example 2</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Example 3</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Example 4</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page-header">
|
||||
<div class="border-bottom pb-2 mb-3">
|
||||
<h1>Token Examples - Transfer is Done</h1>
|
||||
</div>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user