diff --git a/core/src/main/java/org/apache/struts2/action/CspReportAction.java b/core/src/main/java/org/apache/struts2/action/CspReportAction.java new file mode 100644 index 000000000..187e7ee61 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/action/CspReportAction.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.action; + +import com.opensymphony.xwork2.ActionSupport; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.BufferedReader; +import java.io.IOException; + +import static org.apache.struts2.interceptor.csp.CspSettings.CSP_REPORT_TYPE; + +/** + * An abstract Action that can be extended to process the incoming CSP violation reports. Performs + * necessary checks to extract the JSON string of the CSP report and make sure it's a valid report. + * Always returns a 204 response. + * + * Override the processReport(String jsonCspReport) method to customize how the action processes + * the CSP report. See {@link DefaultCspReportAction} for the default implementation. + * + * Add the action to the endpoint that is the reportUri in the {@link org.apache.struts2.interceptor.csp.CspInterceptor} + * to collect the reports. + * + *
+ *     <package name="csp-reports" namespace="/" extends="struts-default">
+ *         <action name="csp-reports" class="org.apache.struts2.action.DefaultCspReportAction">
+ *             <result type="httpheader">
+ *                 <param name="statusCode">200</param>
+ *             </result>
+ *         </action>
+ *     </package>
+ * 
+ * + * @see DefaultCspReportAction + */ +public abstract class CspReportAction extends ActionSupport implements ServletRequestAware, ServletResponseAware { + private HttpServletRequest request; + + @Override + public void withServletRequest(HttpServletRequest request) { + if (!isCspReportRequest(request)) { + return; + } + + try { + BufferedReader reader = request.getReader(); + String cspReport = reader.readLine(); + processReport(cspReport); + } catch (IOException ignored) { + } + } + + private boolean isCspReportRequest(HttpServletRequest request) { + if (!"POST".equals(request.getMethod()) || request.getContentLength() <= 0){ + return false; + } + + String contentType = request.getContentType(); + return CSP_REPORT_TYPE.equals(contentType); + } + + @Override + public void withServletResponse(HttpServletResponse response) { + response.setStatus(204); + } + + abstract void processReport(String jsonCspReport); + + public void setServletRequest(HttpServletRequest request) { + this.request = request; + } + + public HttpServletRequest getServletRequest() { + return request; + } +} diff --git a/core/src/main/java/org/apache/struts2/action/DefaultCspReportAction.java b/core/src/main/java/org/apache/struts2/action/DefaultCspReportAction.java new file mode 100644 index 000000000..3bae9f0ee --- /dev/null +++ b/core/src/main/java/org/apache/struts2/action/DefaultCspReportAction.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.action; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The default implementation of {@link CspReportAction} that simply logs the JSON object + * that contains the details of the CSP violation. + * + * @see CspReportAction + */ +public class DefaultCspReportAction extends CspReportAction { + + protected static final Logger LOG = LogManager.getLogger(DefaultCspReportAction.class); + + @Override + void processReport(String jsonCspReport) { + LOG.error(jsonCspReport); + } +} diff --git a/core/src/main/java/org/apache/struts2/components/Link.java b/core/src/main/java/org/apache/struts2/components/Link.java new file mode 100644 index 000000000..4345c50e1 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/Link.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.struts2.components; + +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.views.annotations.StrutsTag; +import org.apache.struts2.views.annotations.StrutsTagAttribute; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + *

+ * Add nonce propagation feature to implement CSP in link tags + *

+ * + *

+ * The link tag allows the user to load external resources, most usually style sheets. External resources + * can inject malicious code and perform XSS and data injection attacks. The s:link tag includes a nonce + * attribute that is being randomly generated with each request and only allows links with the valid + * nonce value to be executed. + *

+ * + *

Examples

+ * + *
+ *
+ * <s:link ... />
+ *
+ * 
+ * + */ +@StrutsTag(name="link", + tldTagClass="org.apache.struts2.views.jsp.ui.LinkTag", + description="Link tag automatically adds nonces to link elements - should be used in combination with Struts' CSP Interceptor.", + allowDynamicAttributes=true) +public class Link extends UIBean{ + + private static final String TEMPLATE="link"; + + protected String href; + protected String hreflang; + protected String rel; + protected String media; + protected String referrerpolicy; + protected String sizes; + protected String crossorigin; + protected String type; + protected String as; + + public Link(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { + super(stack, request, response); + } + + @StrutsTagAttribute(description="HTML link href attribute") + public void setHref(String href) { + this.href = href; + } + + @StrutsTagAttribute(description="HTML link hreflang attribute") + public void setHreflang(String hreflang) { + this.hreflang = hreflang; + } + + @StrutsTagAttribute(description="HTML link rel attribute") + public void setRel(String rel) { + this.rel = rel; + } + + @StrutsTagAttribute(description="HTML link sizes attribute") + public void setSizes(String sizes) { + this.sizes = sizes; + } + + @StrutsTagAttribute(description="HTML link crossorigin attribute") + public void setCrossorigin(String crossorigin) { + this.crossorigin = crossorigin; + } + + @StrutsTagAttribute(description="HTML link type attribute") + public void setType(String type) { + this.type = type; + } + + @StrutsTagAttribute(description="HTML link as attribute") + public void setAs(String as) { + this.as = as; + } + + @StrutsTagAttribute(description="HTML link media attribute") + public void setMedia(String media) { + this.media = media; + } + + @StrutsTagAttribute(description="HTML link referrerpolicy attribute") + public void setReferrerpolicy(String referrerpolicy) { + this.referrerpolicy = referrerpolicy; + } + + @Override + protected String getDefaultTemplate() { + return TEMPLATE; + } + + @Override + protected void evaluateExtraParams() { + super.evaluateExtraParams(); + + if (href != null) { + addParameter("href", findString(href)); + } + + if (hreflang != null) { + addParameter("hreflang", findString(hreflang)); + } + + if (rel != null) { + addParameter("rel", findString(rel)); + } + + if (media != null) { + addParameter("media", findString(media)); + } + + if (referrerpolicy != null) { + addParameter("referrerpolicy", findString(referrerpolicy)); + } + + if (sizes != null) { + addParameter("sizes", findString(sizes)); + } + + if (crossorigin != null) { + addParameter("crossorigin", findString(crossorigin)); + } + + if (type != null) { + addParameter("type", findString(type)); + } + + if (as != null) { + addParameter("as", findString(as)); + } + + if (disabled != null) { + addParameter("disabled", findString(disabled)); + } + + if (title != null) { + addParameter("title", findString(title)); + } + + if (stack.getActionContext().getSession().containsKey("nonce")) { + String nonceValue = stack.getActionContext().getSession().get("nonce").toString(); + addParameter("nonce", nonceValue); + } + } +} diff --git a/core/src/main/java/org/apache/struts2/components/Script.java b/core/src/main/java/org/apache/struts2/components/Script.java new file mode 100644 index 000000000..f2a208193 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/components/Script.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.components; + +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.views.annotations.StrutsTag; +import org.apache.struts2.views.annotations.StrutsTagAttribute; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + *

+ * Add nonce propagation feature to implement CSP in script tags + *

+ * + *

+ * The script tag allows the user to execute JavaScript. It also allows external resources to execute + * scripts which can be malicious. The s:script tag includes a nonce attribute that is being randomly + * generated with each request and only allows scripts with the valid nonce value to be executed. + *

+ * + *

Examples

+ * + *
+ *
+ * <s:script ... />
+ *
+ * 
+ * + */ +@StrutsTag(name="script", + tldTagClass="org.apache.struts2.views.jsp.ui.ScriptTag", + description="Script tag automatically adds nonces to script blocks - should be used in combination with Struts' CSP Interceptor.", + allowDynamicAttributes=true) +public class Script extends ClosingUIBean { + + protected String async; + protected String charset; + protected String defer; + protected String src; + protected String type; + protected String referrerpolicy; + protected String nomodule; + protected String integrity; + protected String crossorigin; + + private static final String TEMPLATE = "script-close"; + private static final String OPEN_TEMPLATE = "script"; + + public Script(ValueStack stack, HttpServletRequest request, HttpServletResponse response) { + super(stack, request, response); + } + + @Override + public String getDefaultOpenTemplate() { + return OPEN_TEMPLATE; + } + + @Override + protected String getDefaultTemplate() { + return TEMPLATE; + } + + @StrutsTagAttribute(description="HTML script async attribute") + public void setAsync(String async) { + this.async = async; + } + + @StrutsTagAttribute(description="HTML script charset attribute") + public void setCharset(String charset) { + this.charset = charset; + } + + @StrutsTagAttribute(description="HTML script defer attribute") + public void setDefer(String defer) { + this.defer = defer; + } + + @StrutsTagAttribute(description="HTML script src attribute") + public void setSrc(String src) { + this.src = src; + } + + @StrutsTagAttribute(description="HTML script type attribute") + public void setType(String type) { + this.type = type; + } + + @StrutsTagAttribute(description="HTML script referrerpolicy attribute") + public void setReferrerpolicy(String referrerpolicy) { + this.referrerpolicy = referrerpolicy; + } + + @StrutsTagAttribute(description="HTML script nomodule attribute") + public void setNomodule(String nomodule) { + this.nomodule = nomodule; + } + + @StrutsTagAttribute(description="HTML script integrity attribute") + public void setIntegrity(String integrity) { + this.integrity = integrity; + } + + @StrutsTagAttribute(description="HTML script crossorigin attribute") + public void setCrossorigin(String crossorigin) { + this.crossorigin = crossorigin; + } + + @Override + public boolean usesBody() { + return true; + } + + @Override + protected void evaluateExtraParams() { + super.evaluateExtraParams(); + + if (async != null) { + addParameter("async", findString(async)); + } + + if (charset != null) { + addParameter("charset", findString(charset)); + } + + if (defer != null) { + addParameter("defer", findString(defer)); + } + + if (src != null) { + addParameter("src", findString(src)); + } + + if (type != null) { + addParameter("type", findString(type)); + } + + if (referrerpolicy != null) { + addParameter("referrerpolicy", findString(referrerpolicy)); + } + + if (nomodule != null) { + addParameter("nomodule", findString(nomodule)); + } + + if (integrity != null) { + addParameter("integrity", findString(integrity)); + } + + if (crossorigin != null) { + addParameter("crossorigin", findString(crossorigin)); + } + + if (stack.getActionContext().getSession().containsKey("nonce")) { + String nonceValue = stack.getActionContext().getSession().get("nonce").toString(); + addParameter("nonce", nonceValue); + } + } + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java new file mode 100644 index 000000000..250179636 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/csp/CspInterceptor.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor.csp; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.interceptor.PreResultListener; +import java.net.URI; +import java.util.Optional; +import javax.servlet.http.HttpServletResponse; + +/** + * Interceptor that implements Content Security Policy on incoming requests used to protect against + * common XSS and data injection attacks. Uses {@link CspSettings} to add appropriate Content Security Policy header + * to the response. These headers determine what the browser will consider a policy violation and the browser's behavior + * when a violation occurs. A detailed explanation of CSP can be found here. + * + * @see https://csp.withgoogle.com/docs/index.html/ + * @see CspSettings + * @see DefaultCspSettings + **/ +public final class CspInterceptor extends AbstractInterceptor implements PreResultListener { + private final CspSettings settings = new DefaultCspSettings(); + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + invocation.addPreResultListener(this); + return invocation.invoke(); + } + + public void beforeResult(ActionInvocation invocation, String resultCode) { + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + settings.addCspHeaders(response); + } + + public void setReportUri(String reportUri) { + Optional uri = buildUri(reportUri); + if (!uri.isPresent()) { + throw new IllegalArgumentException("Could not parse configured report URI for CSP interceptor: " + reportUri); + } + + if (!uri.get().isAbsolute() && !reportUri.startsWith("/")) { + throw new IllegalArgumentException("Illegal configuration: report URI is not relative to the root. Please set a report URI that starts with /"); + } + + settings.setReportUri(reportUri); + } + + private Optional buildUri(String reportUri) { + try { + return Optional.of(URI.create(reportUri)); + } catch (IllegalArgumentException ignored) { + } + + return Optional.empty(); + } + + public void setEnforcingMode(String value){ + boolean enforcingMode = Boolean.parseBoolean(value); + settings.setEnforcingMode(enforcingMode); + } +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java b/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java new file mode 100644 index 000000000..9699ab291 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/csp/CspSettings.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor.csp; + +import javax.servlet.http.HttpServletResponse; + +/** + * CspSettings interface used by the {@link CspInterceptor} to add the CSP header to the response. + * The default implementation can be found in {@link DefaultCspSettings}. + * + * @see DefaultCspSettings + */ +public interface CspSettings { + + int NONCE_RANDOM_LENGTH = 18; + + String CSP_ENFORCE_HEADER = "Content-Security-Policy"; + String CSP_REPORT_HEADER = "Content-Security-Policy-Report-Only"; + String OBJECT_SRC = "object-src"; + String SCRIPT_SRC = "script-src"; + String BASE_URI = "base-uri"; + String REPORT_URI = "report-uri"; + String NONE = "none"; + String STRICT_DYNAMIC = "strict-dynamic"; + String HTTP = "http:"; + String HTTPS = "https:"; + String CSP_REPORT_TYPE = "application/csp-report"; + + void addCspHeaders(HttpServletResponse response); + // sets the uri where csp violation reports will be sent + void setReportUri(String uri); + // sets CSP headers in enforcing mode when true, and report-only when false + void setEnforcingMode(boolean value); +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java b/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java new file mode 100644 index 000000000..9a3f764a8 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.interceptor.csp; + +import static java.lang.String.format; + +import com.opensymphony.xwork2.ActionContext; + +import java.util.function.Supplier; +import javax.servlet.http.HttpServletResponse; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Map; + + +/** + * Default implementation of {@link CspSettings}. + * The default policy implements strict CSP with a nonce based approach and follows the guide: https://csp.withgoogle.com/docs/index.html/ + * + * @see CspSettings + * @see CspInterceptor + */ +public class DefaultCspSettings implements CspSettings { + private final SecureRandom sRand = new SecureRandom(); + // this lazy supplier computes a policy format the first time it's called and caches the result + // to reduce string operations when attaching policies to HTTP responses + private final Supplier lazyPolicyBuilder = new Supplier() { + boolean hasBeenCalled; + String policyFormat; + + @Override + public String get() { + if (!hasBeenCalled) { + StringBuilder policyFormatBuilder = new StringBuilder() + .append(OBJECT_SRC) + .append(format(" '%s'; ", NONE)) + .append(SCRIPT_SRC) + .append(" 'nonce-%s' ") // nonce placeholder + .append(format("'%s' ", STRICT_DYNAMIC)) + .append(format("%s %s; ", HTTP, HTTPS)) + .append(BASE_URI) + .append(format(" '%s'; ", NONE)); + + if (reportUri != null) { + policyFormatBuilder + .append(REPORT_URI) + .append(format(" %s", reportUri)); + } + + policyFormat = policyFormatBuilder.toString(); + } + + return format(policyFormat, getNonceString()); + } + }; + + private String reportUri; + // default to reporting mode + private String cspHeader = CSP_REPORT_HEADER; + + public void addCspHeaders(HttpServletResponse response) { + associateNonceWithSession(); + response.setHeader(cspHeader, lazyPolicyBuilder.get()); + } + + private String getNonceString() { + Map session = ActionContext.getContext().getSession(); + return (String) session.get("nonce"); + } + + private void associateNonceWithSession() { + Map session = ActionContext.getContext().getSession(); + String nonceValue = Base64.getUrlEncoder().encodeToString(getRandomBytes()); + session.put("nonce", nonceValue); + } + + private byte[] getRandomBytes() { + byte[] ret = new byte[NONCE_RANDOM_LENGTH]; + sRand.nextBytes(ret); + return ret; + } + + public void setEnforcingMode(boolean enforcingMode) { + if (enforcingMode) { + cspHeader = CSP_ENFORCE_HEADER; + } + } + + public void setReportUri(String reportUri) { + this.reportUri = reportUri; + } +} diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/LinkTag.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/LinkTag.java new file mode 100644 index 000000000..4d4aaac40 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/LinkTag.java @@ -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. + */ +package org.apache.struts2.views.jsp.ui; + +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.components.Component; +import org.apache.struts2.components.Link; +import org.apache.struts2.components.Script; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @see Link + */ +public class LinkTag extends AbstractUITag { + + protected String href; + protected String hreflang; + protected String rel; + protected String media; + protected String referrerpolicy; + protected String sizes; + protected String crossorigin; + protected String type; + protected String as; + + @Override + public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { + return new Link(stack, req, res); + } + + protected void populateParams() { + super.populateParams(); + Link link = ((Link) component); + link.setHref(href); + link.setHreflang(hreflang); + link.setRel(rel); + link.setDisabled(disabled); + link.setMedia(media); + link.setReferrerpolicy(referrerpolicy); + link.setSizes(sizes); + link.setCrossorigin(crossorigin); + link.setType(type); + link.setAs(as); + link.setTitle(title); + } + + public void setHref(String href) { + this.href = href; + } + + public void setHreflang(String hreflang) { + this.hreflang = hreflang; + } + + public void setRel(String rel) { + this.rel = rel; + } + + public void setSizes(String sizes) { + this.sizes = sizes; + } + + public void setCrossorigin(String crossorigin) { + this.crossorigin = crossorigin; + } + + public void setType(String type) { + this.type = type; + } + + public void setAs(String as) { + this.as = as; + } + + public void setMedia(String media) { + this.media = media; + } + + public void setReferrerpolicy(String referrerpolicy) { + this.referrerpolicy = referrerpolicy; + } +} diff --git a/core/src/main/java/org/apache/struts2/views/jsp/ui/ScriptTag.java b/core/src/main/java/org/apache/struts2/views/jsp/ui/ScriptTag.java new file mode 100644 index 000000000..40ca19e8c --- /dev/null +++ b/core/src/main/java/org/apache/struts2/views/jsp/ui/ScriptTag.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.views.jsp.ui; + +import com.opensymphony.xwork2.util.ValueStack; +import org.apache.struts2.components.Component; +import org.apache.struts2.components.Form; +import org.apache.struts2.components.Script; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @see Script + */ +public class ScriptTag extends AbstractUITag { + + protected String async; + protected String charset; + protected String defer; + protected String src; + protected String type; + protected String referrerpolicy; + protected String nomodule; + protected String integrity; + protected String crossorigin; + + @Override + public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { + return new Script(stack, req, res); + } + + protected void populateParams() { + super.populateParams(); + Script script = ((Script) component); + script.setAsync(async); + script.setCharset(charset); + script.setDefer(defer); + script.setSrc(src); + script.setType(type); + script.setReferrerpolicy(referrerpolicy); + script.setNomodule(nomodule); + script.setIntegrity(integrity); + script.setCrossorigin(crossorigin); + } + + public void setAsync(String async) { + this.async = async; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + public void setSrc(String src) { + this.src = src; + } + + public void setDefer(String defer) { + this.defer = defer; + } + + public void setType(String type) { + this.type = type; + } + + public void setReferrerpolicy(String referrerpolicy) { + this.referrerpolicy = referrerpolicy; + } + + public void setNomodule(String nomodule) { + this.nomodule = nomodule; + } + + public void setIntegrity(String integrity) { + this.integrity = integrity; + } + + public void setCrossorigin(String crossorigin) { + this.crossorigin = crossorigin; + } +} diff --git a/core/src/main/resources/struts-default.xml b/core/src/main/resources/struts-default.xml index 44a6ad8af..f73a51492 100644 --- a/core/src/main/resources/struts-default.xml +++ b/core/src/main/resources/struts-default.xml @@ -248,6 +248,7 @@ + @@ -379,6 +380,9 @@ + + false + diff --git a/core/src/main/resources/template/simple/link.ftl b/core/src/main/resources/template/simple/link.ftl new file mode 100644 index 000000000..6bb0944e0 --- /dev/null +++ b/core/src/main/resources/template/simple/link.ftl @@ -0,0 +1,65 @@ +<#-- +/* + * 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. + */ +--> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/common-attributes.ftl" /> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/dynamic-attributes.ftl" /> + +<#if parameters.href?has_content> + href="${parameters.href}"<#rt/> + +<#if parameters.hreflang?has_content> + hreflang="${parameters.hreflang}"<#rt/> + +<#if parameters.rel?has_content> + rel="${parameters.rel}"<#rt/> + +<#if parameters.disabled?has_content> + <#if parameters.disabled=="true"> + disabled<#rt/> + + +<#if parameters.media?has_content> + media="${parameters.media}"<#rt/> + +<#if parameters.type?has_content> + type="${parameters.type}"<#rt/> + +<#if parameters.title?has_content> + title="${parameters.title}"<#rt/> + +<#if parameters.as?has_content> + as="${parameters.as}"<#rt/> + +<#if parameters.referrerpolicy?has_content> + referrerpolicy="${parameters.referrerpolicy}"<#rt/> + +<#if parameters.sizes?has_content> + sizes="${parameters.sizes}"<#rt/> + +<#if parameters.crossorigin?has_content> + crossorigin="${parameters.crossorigin}"<#rt/> + +<#if parameters.integrity?has_content> + integrity="${parameters.integrity}"<#rt/> + +<#if parameters.importance?has_content> + importance="${parameters.importance}"<#rt/> + +> diff --git a/core/src/main/resources/template/simple/script-close.ftl b/core/src/main/resources/template/simple/script-close.ftl new file mode 100644 index 000000000..e0f64a41e --- /dev/null +++ b/core/src/main/resources/template/simple/script-close.ftl @@ -0,0 +1,21 @@ +<#-- +/* + * 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. + */ +--> + diff --git a/core/src/main/resources/template/simple/script.ftl b/core/src/main/resources/template/simple/script.ftl new file mode 100644 index 000000000..f16653257 --- /dev/null +++ b/core/src/main/resources/template/simple/script.ftl @@ -0,0 +1,63 @@ +<#-- +/* + * 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. + */ +--> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/common-attributes.ftl" /> +<#include "/${parameters.templateDir}/${parameters.expandTheme}/dynamic-attributes.ftl" /> +