Merge pull request #6393 from luchua-bc/java/xss-jsf

Java: CWE-079 Query to detect XSS with JavaServer Faces (JSF)
This commit is contained in:
Chris Smowton
2021-09-14 15:15:56 +01:00
committed by GitHub
15 changed files with 2203 additions and 10 deletions

View File

@@ -84,6 +84,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.Flexjson
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
private import semmle.code.java.frameworks.JavaxJson
private import semmle.code.java.frameworks.JaxWS
private import semmle.code.java.frameworks.JoddJson

View File

@@ -25,6 +25,7 @@ import semmle.code.java.frameworks.spring.SpringWebClient
import semmle.code.java.frameworks.Guice
import semmle.code.java.frameworks.struts.StrutsActions
import semmle.code.java.frameworks.Thrift
import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
private import semmle.code.java.dataflow.ExternalFlow
/** A data flow source of remote user input. */

View File

@@ -0,0 +1,63 @@
/** Provides classes and predicates for working with JavaServer Faces renderer. */
import java
private import semmle.code.java.dataflow.ExternalFlow
/**
* The JSF class `FacesContext` for processing HTTP requests.
*/
class FacesContext extends RefType {
FacesContext() {
this.hasQualifiedName(["javax.faces.context", "jakarta.faces.context"], "FacesContext")
}
}
private class ExternalContextSource extends SourceModelCsv {
override predicate row(string row) {
row =
["javax.", "jakarta."] +
[
"faces.context;ExternalContext;true;getRequestParameterMap;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestParameterNames;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestParameterValuesMap;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestPathInfo;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestCookieMap;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestHeaderMap;();;ReturnValue;remote",
"faces.context;ExternalContext;true;getRequestHeaderValuesMap;();;ReturnValue;remote"
]
}
}
/**
* The method `getResponseWriter()` declared in JSF `ExternalContext`.
*/
class FacesGetResponseWriterMethod extends Method {
FacesGetResponseWriterMethod() {
getDeclaringType() instanceof FacesContext and
hasName("getResponseWriter") and
getNumberOfParameters() = 0
}
}
/**
* The method `getResponseStream()` declared in JSF `ExternalContext`.
*/
class FacesGetResponseStreamMethod extends Method {
FacesGetResponseStreamMethod() {
getDeclaringType() instanceof FacesContext and
hasName("getResponseStream") and
getNumberOfParameters() = 0
}
}
private class ExternalContextXssSink extends SinkModelCsv {
override predicate row(string row) {
row =
[
"javax.faces.context;ResponseWriter;true;write;;;Argument[0];xss",
"javax.faces.context;ResponseStream;true;write;;;Argument[0];xss",
"jakarta.faces.context;ResponseWriter;true;write;;;Argument[0];xss",
"jakarta.faces.context;ResponseStream;true;write;;;Argument[0];xss"
]
}
}

View File

@@ -5,6 +5,7 @@ import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringHttp
import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking2
import semmle.code.java.dataflow.ExternalFlow
@@ -40,7 +41,7 @@ private class DefaultXssSink extends XssSink {
DefaultXssSink() {
sinkNode(this, "xss")
or
exists(ServletWriterSourceToWritingMethodFlowConfig writer, MethodAccess ma |
exists(XssVulnerableWriterSourceToWritingMethodFlowConfig writer, MethodAccess ma |
ma.getMethod() instanceof WritingMethod and
writer.hasFlowToExpr(ma.getQualifier()) and
this.asExpr() = ma.getArgument(_)
@@ -101,12 +102,14 @@ private class DefaultXSSSanitizer extends XssSanitizer {
}
/** A configuration that tracks data from a servlet writer to an output method. */
private class ServletWriterSourceToWritingMethodFlowConfig extends TaintTracking2::Configuration {
ServletWriterSourceToWritingMethodFlowConfig() {
this = "XSS::ServletWriterSourceToWritingMethodFlowConfig"
private class XssVulnerableWriterSourceToWritingMethodFlowConfig extends TaintTracking2::Configuration {
XssVulnerableWriterSourceToWritingMethodFlowConfig() {
this = "XSS::XssVulnerableWriterSourceToWritingMethodFlowConfig"
}
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ServletWriterSource }
override predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof XssVulnerableWriterSource
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
@@ -128,9 +131,9 @@ private class WritingMethod extends Method {
}
}
/** An output stream or writer that writes to a servlet response. */
class ServletWriterSource extends MethodAccess {
ServletWriterSource() {
/** An output stream or writer that writes to a servlet, JSP or JSF response. */
class XssVulnerableWriterSource extends MethodAccess {
XssVulnerableWriterSource() {
this.getMethod() instanceof ServletResponseGetWriterMethod
or
this.getMethod() instanceof ServletResponseGetOutputStreamMethod
@@ -139,9 +142,18 @@ class ServletWriterSource extends MethodAccess {
m.getDeclaringType().getQualifiedName() = "javax.servlet.jsp.JspContext" and
m.getName() = "getOut"
)
or
this.getMethod() instanceof FacesGetResponseWriterMethod
or
this.getMethod() instanceof FacesGetResponseStreamMethod
}
}
/**
* DEPRECATED: Use `XssVulnerableWriterSource` instead.
*/
deprecated class ServletWriterSource = XssVulnerableWriterSource;
/**
* Holds if `s` is an HTTP Content-Type vulnerable to XSS.
*/

View File

@@ -36,7 +36,9 @@ class ServletWriterSourceToPrintStackTraceMethodFlowConfig extends TaintTracking
this = "StackTraceExposure::ServletWriterSourceToPrintStackTraceMethodFlowConfig"
}
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ServletWriterSource }
override predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof XssVulnerableWriterSource
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |

View File

@@ -0,0 +1,69 @@
import java.io.IOException;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
import javax.servlet.http.Cookie;
@FacesRenderer(componentFamily = "", rendererType = "")
public class JsfXSS extends Renderer
{
@Override
// BAD: directly output user input.
public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
{
super.encodeBegin(facesContext, component);
Map<String, String> requestParameters = facesContext.getExternalContext().getRequestParameterMap();
String windowId = requestParameters.get("window_id");
ResponseWriter writer = facesContext.getResponseWriter();
writer.write("<script type=\"text/javascript\">");
writer.write("(function(){");
writer.write("dswh.init('" + windowId + "','" // $xss
+ "......" + "',"
+ -1 + ",{");
writer.write("});");
writer.write("})();");
writer.write("</script>");
}
// GOOD: use the method `writeText` that performs escaping appropriate for the markup language being rendered.
public void encodeBegin2(FacesContext facesContext, UIComponent component) throws IOException
{
super.encodeBegin(facesContext, component);
Map<String, String> requestParameters = facesContext.getExternalContext().getRequestParameterMap();
String windowId = requestParameters.get("window_id");
ResponseWriter writer = facesContext.getResponseWriter();
writer.write("<script type=\"text/javascript\">");
writer.write("(function(){");
writer.write("dswh.init('");
writer.writeText(windowId, null);
writer.write("','"
+ "......" + "',"
+ -1 + ",{");
writer.write("});");
writer.write("})();");
writer.write("</script>");
}
public void testAllSources(FacesContext facesContext) throws IOException
{
ExternalContext ec = facesContext.getExternalContext();
ResponseWriter writer = facesContext.getResponseWriter();
writer.write(ec.getRequestParameterMap().keySet().iterator().next()); // $xss
writer.write(ec.getRequestParameterNames().next()); // $xss
writer.write(ec.getRequestParameterValuesMap().get("someKey")[0]); // $xss
writer.write(ec.getRequestParameterValuesMap().keySet().iterator().next()); // $xss
writer.write(ec.getRequestPathInfo()); // $xss
writer.write(((Cookie)ec.getRequestCookieMap().get("someKey")).getName()); // $xss
writer.write(ec.getRequestHeaderMap().get("someKey")); // $xss
writer.write(ec.getRequestHeaderValuesMap().get("someKey")[0]); // $xss
}
}

View File

@@ -1 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/javax-faces-2.3/

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.component;
import java.util.Map;
/**
* <p>
* <strong class="changed_modified_2_0 changed_modified_2_0_rev_a changed_modified_2_1
* changed_modified_2_2 changed_modified_2_3">UIComponent</strong> is the base class for
* all user interface components in Jakarta Server Faces. The set of {@link UIComponent}
* instances associated with a particular request and response are organized into a
* component tree under a {@link UIViewRoot} that represents the entire content of the
* request or response.
* </p>
*
* <p>
* For the convenience of component developers, {@link UIComponentBase} provides the
* default behavior that is specified for a {@link UIComponent}, and is the base class for
* all of the concrete {@link UIComponent} "base" implementations. Component writers are
* encouraged to subclass {@link UIComponentBase}, instead of directly implementing this
* abstract class, to reduce the impact of any future changes to the method signatures.
* </p>
*
* <p class="changed_added_2_0">
* If the {@link javax.faces.event.ListenerFor} annotation is attached to the class
* definition of a <code>Component</code>, that class must also implement
* {@link javax.faces.event.ComponentSystemEventListener}.
* </p>
*
* <p class="changed_added_2_3">
* Dynamically modifying the component tree can happen at any time, during and after
* restoring the view, but not during state saving and needs to function properly with
* respect to rendering and state saving
* </p>
*/
public abstract class UIComponent {
/**
* <p>
* Return a mutable <code>Map</code> representing the attributes (and properties, see
* below) associated wth this {@link UIComponent}, keyed by attribute name (which must
* be a String). The returned implementation must support all of the standard and
* optional <code>Map</code> methods, plus support the following additional
* requirements:
* </p>
* <ul>
* <li>The <code>Map</code> implementation must implement the
* <code>java.io.Serializable</code> interface.</li>
* <li>Any attempt to add a <code>null</code> key or value must throw a
* <code>NullPointerException</code>.</li>
* <li>Any attempt to add a key that is not a String must throw a
* <code>ClassCastException</code>.</li>
* <li>If the attribute name specified as a key matches a property of this
* {@link UIComponent}'s implementation class, the following methods will have special
* behavior:
* <ul>
* <li><code>containsKey</code> - Return <code>false</code>.</li>
* <li><code>get()</code> - If the property is readable, call the getter method and
* return the returned value (wrapping primitive values in their corresponding wrapper
* classes); otherwise throw <code>IllegalArgumentException</code>.</li>
* <li><code>put()</code> - If the property is writeable, call the setter method to
* set the corresponding value (unwrapping primitive values in their corresponding
* wrapper classes). If the property is not writeable, or an attempt is made to set a
* property of primitive type to <code>null</code>, throw
* <code>IllegalArgumentException</code>.</li>
* <li><code>remove</code> - Throw <code>IllegalArgumentException</code>.</li>
* </ul>
* </li>
* </ul>
*
* @return the component attribute map.
*/
public abstract Map<String, Object> getAttributes();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.context;
import java.util.Iterator;
import java.util.Map;
/**
* <p><strong class="changed_modified_2_0 changed_modified_2_1
* changed_modified_2_2">FacesContext</strong> contains all of the
* per-request state information related to the processing of a single
* Jakarta Server Faces request, and the rendering of the corresponding
* response. It is passed to, and potentially modified by, each phase
* of the request processing lifecycle.</p>
*
* <p>A {@link FacesContext} instance is associated with a particular
* request at the beginning of request processing, by a call to the
* <code>getFacesContext()</code> method of the {@link FacesContextFactory}
* instance associated with the current web application. The instance
* remains active until its <code>release()</code> method is called, after
* which no further references to this instance are allowed. While a
* {@link FacesContext} instance is active, it must not be referenced
* from any thread other than the one upon which the Jakarta Servlet container
* executing this web application utilizes for the processing of this request.
* </p>
*
* <p class="changed_added_2_3">A FacesContext can be injected into a request
* scoped bean using <code>@Inject FacesContext facesContext;</code>
* </p>
*/
public abstract class FacesContext {
public FacesContext() {
}
/**
* <p class="changed_added_2_0">Return a mutable <code>Map</code>
* representing the attributes associated wth this
* <code>FacesContext</code> instance. This <code>Map</code> is
* useful to store attributes that you want to go out of scope when the
* Faces lifecycle for the current request ends, which is not always the same
* as the request ending, especially in the case of Jakarta Servlet filters
* that are invoked <strong>after</strong> the Faces lifecycle for this
* request completes. Accessing this <code>Map</code> does not cause any
* events to fire, as is the case with the other maps: for request, session, and
* application scope. When {@link #release()} is invoked, the attributes
* must be cleared.</p>
*
* <div class="changed_added_2_0">
*
* <p>The <code>Map</code> returned by this method is not associated with
* the request. If you would like to get or set request attributes,
* see {@link ExternalContext#getRequestMap}.
*
* <p>The default implementation throws
* <code>UnsupportedOperationException</code> and is provided
* for the sole purpose of not breaking existing applications that extend
* this class.</p>
*
* </div>
*
* @return mutable <code>Map</code> representing the attributes associated wth this
* <code>FacesContext</code> instance.
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*
* @since 2.0
*/
public Map<Object, Object> getAttributes() {
return null;
}
/**
* <p>Return an <code>Iterator</code> over the client identifiers for
* which at least one {@link javax.faces.application.FacesMessage} has been queued. If there are no
* such client identifiers, an empty <code>Iterator</code> is returned.
* If any messages have been queued that were not associated with any
* specific client identifier, a <code>null</code> value will be included
* in the iterated values. The elements in the <code>Iterator</code> must
* be returned in the order in which they were added with {@link #addMessage}.</p>
*
* @return the <code>Iterator</code> over the client identifiers for
* which at least one {@link javax.faces.application.FacesMessage} has been queued.
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*/
public abstract Iterator<String> getClientIdsWithMessages();
/**
* <p><span class="changed_modified_2_0">Return</span> the {@link
* ExternalContext} instance for this <code>FacesContext</code>
* instance.</p>
* <p class="changed_added_2_0">It is valid to call this method
* during application startup or shutdown. If called during application
* startup or shutdown, this method returns an {@link ExternalContext} instance
* with the special behaviors indicated in the javadoc for that
* class. Methods document as being valid to call during
* application startup or shutdown must be supported.</p>
*
* @return instance of <code>ExternalContext</code>
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*/
public abstract ExternalContext getExternalContext();
/**
* <p>Return the {@link ResponseStream} to which components should
* direct their binary output. Within a given response, components
* can use either the ResponseStream or the ResponseWriter,
* but not both.
*
* @return <code>ResponseStream</code> instance.
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*/
public abstract ResponseStream getResponseStream();
/**
* <p>Set the {@link ResponseStream} to which components should
* direct their binary output.
*
* @param responseStream The new ResponseStream for this response
*
* @throws NullPointerException if <code>responseStream</code>
* is <code>null</code>
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*/
public abstract void setResponseStream(ResponseStream responseStream);
/**
* <p>Return the {@link ResponseWriter} to which components should
* direct their character-based output. Within a given response,
* components can use either the ResponseStream or the ResponseWriter,
* but not both.</p>
*
* @return <code>ResponseWriter</code> instance.
*
* @throws IllegalStateException if this method is called after
* this instance has been released
*/
public abstract ResponseWriter getResponseWriter();
/**
* <p>Set the {@link ResponseWriter} to which components should
* direct their character-based output.
*
* @param responseWriter The new ResponseWriter for this response
*
* @throws IllegalStateException if this method is called after
* this instance has been released
* @throws NullPointerException if <code>responseWriter</code>
* is <code>null</code>
*/
public abstract void setResponseWriter(ResponseWriter responseWriter);
/**
* <p class="changed_modified_2_0">Return the {@link FacesContext}
* instance for the request that is being processed by the current
* thread. If called during application initialization or shutdown,
* any method documented as "valid to call this method during
* application startup or shutdown" must be supported during
* application startup or shutdown time. The result of calling a
* method during application startup or shutdown time that does not
* have this designation is undefined.</p>
*
* @return the instance of <code>FacesContext</code>.
*/
public static FacesContext getCurrentInstance() {
return null;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.context;
import java.io.OutputStream;
/**
* <p><strong>ResponseStream</strong> is an interface describing an adapter
* to an underlying output mechanism for binary output.</p>
*/
public abstract class ResponseStream extends OutputStream {
}

View File

@@ -0,0 +1,372 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.context;
import javax.faces.component.UIComponent;
import java.io.IOException;
import java.io.Writer;
/**
* <p><span
* class="changed_modified_2_2"><strong>ResponseWriter</strong></span>
* is an abstract class describing an adapter to an underlying output
* mechanism for character-based output. In addition to the low-level
* <code>write()</code> methods inherited from
* <code>java.io.Writer</code>, this class provides utility methods that
* are useful in producing elements and attributes for markup languages
* like HTML and XML.</p>
*/
public abstract class ResponseWriter extends Writer {
/**
* <p>Return the content type (such as "text/html") for this {@link
* ResponseWriter}. Note: this must not include the "charset="
* suffix.</p>
*
* @return the content type
*/
public abstract String getContentType();
/**
* <p>Return the character encoding (such as "ISO-8859-1") for this
* {@link ResponseWriter}. Please see <a
* href="http://www.iana.org/assignments/character-sets">the
* IANA</a> for a list of character encodings.</p>
*
* @return the character encoding
*/
public abstract String getCharacterEncoding();
/**
* <p>Flush any ouput buffered by the output method to the
* underlying Writer or OutputStream. This method
* will not flush the underlying Writer or OutputStream; it
* simply clears any values buffered by this {@link ResponseWriter}.</p>
*/
@Override
public abstract void flush() throws IOException;
/**
* <p>Write whatever text should begin a response.</p>
*
* @throws IOException if an input/output error occurs
*/
public abstract void startDocument() throws IOException;
/**
* <p>Write whatever text should end a response. If there is an open
* element that has been created by a call to <code>startElement()</code>,
* that element will be closed first.</p>
*
* @throws IOException if an input/output error occurs
*/
public abstract void endDocument() throws IOException;
/**
* <p><span class="changed_modified_2_2">Write</span> the start of an element,
up to and including the
* element name. Once this method has been called, clients can
* call the <code>writeAttribute()</code> or
* <code>writeURIAttribute()</code> methods to add attributes and
* corresponding values. The starting element will be closed
* (that is, the trailing '&gt;' character added)
* on any subsequent call to <code>startElement()</code>,
* <code>writeComment()</code>,
* <code>writeText()</code>, <code>endElement()</code>,
* <code>endDocument()</code>, <code>close()</code>,
* <code>flush()</code>, or <code>write()</code>.</p>
*
* <div class="changed_added_2_2">
*
* <p>If the argument component's pass through attributes
* includes an attribute of the name given by the value of the symbolic
* constant {@link javax.faces.render.Renderer#PASSTHROUGH_RENDERER_LOCALNAME_KEY},
* use that as the element name, instead of the value passed as the first
* parameter to this method. Care must be taken so that this value
* is not also rendered when any other pass through attributes on this component
* are rendered.</p>
*
* </div>
*
* @param name Name of the element to be started
* @param component The {@link UIComponent} (if any) to which this
* element corresponds. <span
* class="changed_added_2_2"> This component is
* inspected for its pass through attributes as
* described in the standard HTML_BASIC {@code
* RenderKit} specification.</span>
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code>
* is <code>null</code>
*/
public abstract void startElement(String name, UIComponent component)
throws IOException;
/**
* <p><span class="changed_modified_2_2">Write</span> the end of an element,
* after closing any open element
* created by a call to <code>startElement()</code>. Elements must be
* closed in the inverse order from which they were opened; it is an
* error to do otherwise.</p>
*
* <div class="changed_added_2_2">
*
* <p>If the argument component's pass through attributes
* includes an attribute of the name given by the value of the symbolic
* constant {@link javax.faces.render.Renderer#PASSTHROUGH_RENDERER_LOCALNAME_KEY},
* use that as the element name, instead of the value passed as the first
* parameter to this method.</p>
*
* </div>
*
* @param name Name of the element to be ended
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code>
* is <code>null</code>
*/
public abstract void endElement(String name) throws IOException;
/**
* <p>Write an attribute name and corresponding value, after converting
* that text to a String (if necessary), and after performing any escaping
* appropriate for the markup language being rendered.
* This method may only be called after a call to
* <code>startElement()</code>, and before the opened element has been
* closed.</p>
*
* @param name Attribute name to be added
* @param value Attribute value to be added
* @param property Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element,
* to which this generated attribute corresponds
* @throws IllegalStateException if this method is called when there
* is no currently open element
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> is
* <code>null</code>
*/
public abstract void writeAttribute(String name, Object value,
String property)
throws IOException;
/**
* <p><span class="changed_modified_2_2">Write</span> a URI
* attribute name and corresponding value, after converting that
* text to a String (if necessary), and after performing any
* encoding <span class="changed_modified_2_2">or escaping</span>
* appropriate to the markup language being rendered. <span
* class="changed_modified_2_2">When rendering in a WWW environment,
* the escaping conventions established in the W3C URI spec document
* &lt;<a
* href="http://www.w3.org/Addressing/URL/uri-spec.html">http://www.w3.org/Addressing/URL/uri-spec.html</a>&gt;
* must be followed. In particular, spaces ' ' must be encoded as
* %20 and not the plus character '+'.</span> This method may only
* be called after a call to <code>startElement()</code>, and before
* the opened element has been closed.</p>
*
* @param name Attribute name to be added
* @param value Attribute value to be added
* @param property Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element,
* to which this generated attribute corresponds
* @throws IllegalStateException if this method is called when there
* is no currently open element
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>name</code> is
* <code>null</code>
*/
public abstract void writeURIAttribute(String name, Object value,
String property)
throws IOException;
/**
* <p class="changed_added_2_0">Open an XML <code>CDATA</code>
* block. Note that XML does not allow nested <code>CDATA</code>
* blocks, though this method does not enforce that constraint. The
* default implementation of this method takes no action when
* invoked.</p>
* @throws IOException if input/output error occures
*/
public void startCDATA() throws IOException {
}
/**
* <p class="changed_added_2_0">Close an XML <code>CDATA</code>
* block. The default implementation of this method takes no action
* when invoked.</p>
* @throws IOException if input/output error occures
*/
public void endCDATA() throws IOException {
throw new UnsupportedOperationException();
}
/**
* <p>Write a comment containing the specified text, after converting
* that text to a String (if necessary), and after performing any escaping
* appropriate for the markup language being rendered. If there is
* an open element that has been created by a call to
* <code>startElement()</code>, that element will be closed first.</p>
*
* @param comment Text content of the comment
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>comment</code>
* is <code>null</code>
*/
public abstract void writeComment(Object comment) throws IOException;
/**
* <p class="changed_added_2_2">Write a string containing the markup specific
* preamble.
* No escaping is performed. The default
* implementation simply calls through to {@link #write(java.lang.String)} .</p>
*
* <div class="changed_added_2_2">
*
* <p>The implementation makes no checks if this is the correct place
* in the response to have a preamble, nor does it prevent the preamble
* from being written more than once.</p>
*
* </div>
*
* @since 2.2
* @param preamble Text content of the preamble
* @throws IOException if an input/output error occurs
*/
public void writePreamble(String preamble) throws IOException {
write(preamble);
}
/**
* <p class="changed_added_2_2">Write a string containing the markup specific
* doctype.
* No escaping is performed. The default
* implementation simply calls through to {@link #write(java.lang.String)} .</p>
*
* <div class="changed_added_2_2">
*
* <p>The implementation makes no checks if this is the correct place
* in the response to have a doctype, nor does it prevent the doctype
* from being written more than once.</p>
*
* </div>
*
* @since 2.2
* @param doctype Text content of the doctype
* @throws IOException if an input/output error occurs
*/
public void writeDoctype(String doctype) throws IOException {
write(doctype);
}
/**
* <p>Write an object, after converting it to a String (if necessary),
* and after performing any escaping appropriate for the markup language
* being rendered. If there is an open element that has been created
* by a call to <code>startElement()</code>, that element will be closed
* first.</p>
*
* @param text Text to be written
* @param property Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element,
* to which this generated text corresponds
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code>
* is <code>null</code>
*/
public abstract void writeText(Object text, String property)
throws IOException;
/**
* <p>Write an object, after converting it to a String (if
* necessary), and after performing any escaping appropriate for the
* markup language being rendered. This method is equivalent to
* {@link #writeText(java.lang.Object,java.lang.String)} but adds a
* <code>component</code> property to allow custom
* <code>ResponseWriter</code> implementations to associate a
* component with an arbitrary portion of text.</p>
*
* <p>The default implementation simply ignores the
* <code>component</code> argument and calls through to {@link
* #writeText(java.lang.Object,java.lang.String)}</p>
*
* @param text Text to be written
* @param component The {@link UIComponent} (if any) to which
* this element corresponds
* @param property Name of the property or attribute (if any) of the
* {@link UIComponent} associated with the containing element,
* to which this generated text corresponds
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code>
* is <code>null</code>
* @since 1.2
*/
public void writeText(Object text, UIComponent component, String property)
throws IOException {
writeText(text, property);
}
/**
* <p>Write text from a character array, after any performing any
* escaping appropriate for the markup language being rendered.
* If there is an open element that has been created by a call to
* <code>startElement()</code>, that element will be closed first.</p>
*
* @param text Text to be written
* @param off Starting offset (zero-relative)
* @param len Number of characters to be written
* @throws IndexOutOfBoundsException if the calculated starting or
* ending position is outside the bounds of the character array
* @throws IOException if an input/output error occurs
* @throws NullPointerException if <code>text</code>
* is <code>null</code>
*/
public abstract void writeText(char text[], int off, int len)
throws IOException;
/**
* <p>Create and return a new instance of this {@link ResponseWriter},
* using the specified <code>Writer</code> as the output destination.</p>
*
* @param writer The <code>Writer</code> that is the output destination
*
* @return the new <code>ResponseWriter</code>
*/
public abstract ResponseWriter cloneWithWriter(Writer writer);
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.render;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;
/**
* <p class="changed_added_2_0">The presence of this annotation on a
* class automatically registers the class with the runtime as a {@link
* Renderer}. The value of the {@link #renderKitId} attribute is taken
* to be the <em>render-kit-id</em> to which an instance of this
* <code>Renderer</code> is to be added. There must be a public
* zero-argument constructor on any class where this annotation appears.
* The implementation must indicate a fatal error if such a constructor
* does not exist and the application must not be placed in service.
* Within that {@link RenderKit}, The value of the {@link #rendererType}
* attribute is taken to be the <em>renderer-type</em>, and the value of
* the {@link #componentFamily} attribute is to be taken as the
* <em>component-family</em>. The implementation must guarantee that
* for each class annotated with <code>FacesRenderer</code>, found with
* the algorithm in section JSF.11.5,
* the following actions are taken.</p>
* <div class="changed_added_2_0">
* <ul>
* <li><p>Obtain a reference to the {@link RenderKitFactory} for
* this application.</p></li>
<li><p>See if a <code>RenderKit</code> exists for
<em>render-kit-id</em>. If so, let that instance be
<em>renderKit</em> for discussion. If not, the implementation
must indicate a fatal error if such a <code>RenderKit</code>
does not exist and the application must not be placed in
service.</p></li>
<li><p>Create an instance of this class using the public
zero-argument constructor.</p></li>
<li><p>Call {@link RenderKit#addRenderer} on
<em>renderKit</em>, passing <em>component-family</em> as the
first argument, <em>renderer-type</em> as the second, and the
newly instantiated <code>RenderKit</code> instance as the
third argument.</p></li>
* </ul>
* </div>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface FacesRenderer {
/**
* <p class="changed_added_2_0">The value of this annotation
* attribute is taken to be the <em>render-kit-id</em> in which an
* instance of this class of <code>Renderer</code> must be
* installed.</p>
*
* @return the <em>render-kit-id</em>
*/
String renderKitId() default "";
/**
* <p class="changed_added_2_0">The value of this annotation
* attribute is taken to be the <em>renderer-type</em> which, in
* combination with {@link #componentFamily} can be used to obtain a
* reference to an instance of this {@link Renderer} by calling
* {@link javax.faces.render.RenderKit#getRenderer(java.lang.String,
* java.lang.String)}.</p>
*
* @return the <em>renderer-type</em>
*/
String rendererType();
/**
* <p class="changed_added_2_0">The value of this annotation
* attribute is taken to be the <em>component-family</em> which, in
* combination with {@link #rendererType} can be used to obtain a
* reference to an instance of this {@link Renderer} by calling
* {@link javax.faces.render.RenderKit#getRenderer(java.lang.String,
* java.lang.String)}.</p>
*
* @return the <em>component-family</em>
*/
String componentFamily();
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces.render;
import java.io.IOException;
import java.util.Iterator;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
/**
* <p>A <strong class="changed_modified_2_0 changed_modified_2_2">Renderer</strong> converts
* the internal representation of {@link UIComponent}s into the output
* stream (or writer) associated with the response we are creating for a
* particular request. Each <code>Renderer</code> knows how to render
* one or more {@link UIComponent} types (or classes), and advertises a
* set of render-dependent attributes that it recognizes for each
* supported {@link UIComponent}.</p>
*
* <p>Families of {@link Renderer}s are packaged as a {@link RenderKit},
* and together support the rendering of all of the {@link UIComponent}s
* in a view associated with a {@link FacesContext}. Within the set of
* {@link Renderer}s for a particular {@link RenderKit}, each must be
* uniquely identified by the <code>rendererType</code> property.</p>
*
* <p>Individual {@link Renderer} instances will be instantiated as requested
* during the rendering process, and will remain in existence for the
* remainder of the lifetime of a web application. Because each instance
* may be invoked from more than one request processing thread simultaneously,
* they MUST be programmed in a thread-safe manner.</p>
*
* <div class="changed_added_2_0">
* <p>If the {@link javax.faces.event.ListenerFor} annotation is
* attached to the class definition of a <code>Renderer</code>, that
* class must also implement {@link
* javax.faces.event.ComponentSystemEventListener}, and the action
* pertaining to the processing of <code>ResourceDependency</code> on a
* <code>Renderer</code> described in {@link
* javax.faces.event.ListenerFor} must be taken. </p>
* <p>If the {@link javax.faces.application.ResourceDependency}
* annotation is attached to the class definition of a
* <code>Renderer</code>, the action pertaining to the processing of
* <code>ResourceDependency</code> on a <code>Renderer</code> described
* in {@link UIComponent#getChildren} must be taken. </p>
* </div>
*/
public abstract class Renderer {
/**
* <p>Decode any new state of the specified {@link UIComponent}
* from the request contained in the specified {@link FacesContext},
* and store that state on the {@link UIComponent}.</p>
*
* <p>During decoding, events may be enqueued for later processing
* (by event listeners that have registered an interest), by calling
* <code>queueEvent()</code> on the associated {@link UIComponent}.
* </p>
*
* @param context {@link FacesContext} for the request we are processing
* @param component {@link UIComponent} to be decoded.
*
* @throws NullPointerException if <code>context</code>
* or <code>component</code> is <code>null</code>
*/
public void decode(FacesContext context, UIComponent component) {
}
/**
* <p>Render the beginning specified {@link UIComponent} to the
* output stream or writer associated with the response we are creating.
* If the conversion attempted in a previous call to
* <code>getConvertedValue()</code> for this component failed, the state
* information saved during execution
* of <code>decode()</code> should be used to reproduce the incorrect
* input.</p>
*
* @param context {@link FacesContext} for the request we are processing
* @param component {@link UIComponent} to be rendered
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if <code>context</code>
* or <code>component</code> is null
*/
public void encodeBegin(FacesContext context,
UIComponent component)
throws IOException {
}
/**
* <p>Render the child components of this {@link UIComponent}, following
* the rules described for <code>encodeBegin()</code> to acquire the
* appropriate value to be rendered. This method will only be called
* if the <code>rendersChildren</code> property of this component
* is <code>true</code>.</p>
*
* @param context {@link FacesContext} for the response we are creating
* @param component {@link UIComponent} whose children are to be rendered
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if <code>context</code>
* or <code>component</code> is <code>null</code>
*/
public void encodeChildren(FacesContext context, UIComponent component)
throws IOException {
}
/**
* <p>Render the ending of the current state of the specified
* {@link UIComponent}, following the rules described for
* <code>encodeBegin()</code> to acquire the appropriate value
* to be rendered.</p>
*
* @param context {@link FacesContext} for the response we are creating
* @param component {@link UIComponent} to be rendered
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if <code>context</code>
* or <code>component</code> is <code>null</code>
*/
public void encodeEnd(FacesContext context,
UIComponent component)
throws IOException {
}
/**
* <p>Convert the component generated client id to a form suitable
* for transmission to the client.</p>
*
* <p>The default implementation returns the argument
* <code>clientId</code> unchanged.</p>
*
* @param context {@link FacesContext} for the current request
* @param clientId the client identifier to be converted to client a
* specific format.
*
* @throws NullPointerException if <code>context</code>
* or <code>clientId</code> is <code>null</code>
*
* @return the converted {@code clientId}
*/
public String convertClientId(FacesContext context, String clientId) {
return null;
}
}