/**
* Copyright (c) 2000-2017 Liferay, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.liferay.faces.util.render;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.AjaxBehavior;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import com.liferay.faces.util.component.ComponentUtil;
import com.liferay.faces.util.component.Styleable;
import com.liferay.faces.util.logging.Logger;
import com.liferay.faces.util.logging.LoggerFactory;
/**
* @author Neil Griffin
*/
public class RendererUtil {
// Private Constants
private static final String JAVA_SCRIPT_HEX_PREFIX = "\\x";
private static final char[] _HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
// Logger
private static final Logger logger = LoggerFactory.getLogger(RendererUtil.class);
public static void addDefaultAjaxBehavior(ClientBehaviorHolder clientBehaviorHolder, String execute, String process,
String defaultExecute, String render, String update, String defaultRender) {
Map<String, List<ClientBehavior>> clientBehaviorMap = clientBehaviorHolder.getClientBehaviors();
String defaultEventName = clientBehaviorHolder.getDefaultEventName();
List<ClientBehavior> clientBehaviors = clientBehaviorMap.get(defaultEventName);
boolean doAdd = true;
if (clientBehaviors != null) {
for (ClientBehavior clientBehavior : clientBehaviors) {
if (clientBehavior instanceof AjaxBehavior) {
doAdd = false;
break;
}
}
}
if (doAdd) {
FacesContext facesContext = FacesContext.getCurrentInstance();
Application application = facesContext.getApplication();
AjaxBehavior ajaxBehavior = (AjaxBehavior) application.createBehavior(AjaxBehavior.BEHAVIOR_ID);
Collection<String> executeIds = getExecuteIds(execute, process, defaultExecute);
ajaxBehavior.setExecute(executeIds);
Collection<String> renderIds = getRenderIds(render, update, defaultRender);
ajaxBehavior.setRender(renderIds);
clientBehaviorHolder.addClientBehavior(defaultEventName, ajaxBehavior);
}
}
public static void decodeClientBehaviors(FacesContext facesContext, UIComponent uiComponent) {
if (uiComponent instanceof ClientBehaviorHolder) {
ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) uiComponent;
Map<String, List<ClientBehavior>> clientBehaviorMap = clientBehaviorHolder.getClientBehaviors();
Map<String, String> requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
String behaviorEvent = requestParameterMap.get("javax.faces.behavior.event");
if (behaviorEvent != null) {
List<ClientBehavior> clientBehaviors = clientBehaviorMap.get(behaviorEvent);
if (clientBehaviors != null) {
String source = requestParameterMap.get("javax.faces.source");
if (source != null) {
String clientId = uiComponent.getClientId(facesContext);
if (clientId.startsWith(source)) {
for (ClientBehavior behavior : clientBehaviors) {
behavior.decode(facesContext, uiComponent);
}
}
}
}
}
}
}
public static void encodeStyleable(ResponseWriter responseWriter, Styleable styleable, String... classNames)
throws IOException {
StringBuilder allCssClasses = new StringBuilder();
String cssClasses = ComponentUtil.concatCssClasses(classNames);
if (cssClasses != null) {
allCssClasses.append(cssClasses);
allCssClasses.append(" ");
}
String styleClass = styleable.getStyleClass();
if (styleClass != null) {
allCssClasses.append(styleClass);
}
if (allCssClasses.length() > 0) {
responseWriter.writeAttribute("class", allCssClasses.toString(), Styleable.STYLE_CLASS);
}
String style = styleable.getStyle();
if (style != null) {
responseWriter.writeAttribute(Styleable.STYLE, style, Styleable.STYLE);
}
}
/**
* Escapes JavaScript so that it can safely be rendered as string in the browser. This method escapes JS according
* to the recommendations provided in <a
* href="https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.233_-_JavaScript_Escape_Before_Inserting_Untrusted_Data_into_JavaScript_Data_Values">
* OWASP's Cross Site Scripting (XSS) Prevention Cheat Sheet</a>. <strong>Note:</strong> escaped JS can only be
* rendered into certain JS strings. If it is rendered anywhere else, the site will still be vulnerable to XSS. See
* the link for more details.
*
* @param javaScript a JavaScript string which has not been escaped to prevent XSS.
*
* @return an escaped JavaScript string.
*/
public static String escapeJavaScript(String javaScript) {
StringBuilder stringBuilder = new StringBuilder();
char[] javaScriptCharArray = javaScript.toCharArray();
for (char character : javaScriptCharArray) {
if ((character > 255) || Character.isLetterOrDigit(character)) {
stringBuilder.append(character);
}
else {
stringBuilder.append(JAVA_SCRIPT_HEX_PREFIX);
String hexString = toHexString(character);
if (hexString.length() == 1) {
stringBuilder.append("0");
}
stringBuilder.append(hexString);
}
}
if (stringBuilder.length() != javaScript.length()) {
javaScript = stringBuilder.toString();
}
return javaScript;
}
private static Collection<String> getExecuteIds(String execute, String process, String defaultValue) {
// If the values of the execute and process attributes differ, then
if (!execute.equals(process)) {
// If the process attribute was specified and the execute attribute was omitted, then use the value of the
// process attribute.
if (execute.equals(defaultValue)) {
execute = process;
}
// Otherwise, if both the execute and process attributes were specified with different values, then log a
// warning indicating that the value of the execute attribute takes precedence.
else if (!process.equals(defaultValue)) {
logger.warn(
"Different values were specified for the execute=[{0}] and process=[{0}]. The value for execute takes precedence.");
}
}
return Arrays.asList(execute.split(" "));
}
private static Collection<String> getRenderIds(String render, String update, String defaultValue) {
// If the values of the render and update attributes differ, then
if (!render.equals(update)) {
// If the update attribute was specified and the render attribute was omitted, then use the value of the
// update attribute.
if (render.equals(defaultValue)) {
render = update;
}
// Otherwise, if both the render and update attributes were specified with different values, then log a
// warning indicating that the value of the render attribute takes precedence.
else if (!update.equals(defaultValue)) {
logger.warn(
"Different values were specified for the render=[{0}] and update=[{0}]. The value for render takes precedence.");
}
}
return Arrays.asList(render.split(" "));
}
private static String toHexString(int i) {
char[] buffer = new char[8];
int index = 8;
do {
buffer[--index] = _HEX_DIGITS[i & 15];
i >>>= 4;
}
while (i != 0);
return new String(buffer, index, 8 - index);
}
}