/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.dashbuilder.displayer.client.widgets.sourcecode;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import org.dashbuilder.common.client.StringTemplateBuilder;
import org.dashbuilder.displayer.client.resources.i18n.SourceCodeValidatorConstants;
@Dependent
public class DefaultJsValidator implements JsValidator {
public static final String[] _jsMalicious = {"document.", "window.", "eval(", "eval ", "eval\t", "eval\n", ".innerHTML"};
protected JsEvaluator jsEvaluator;
protected StringTemplateBuilder codeBuilder;
protected StringTemplateBuilder restoreBuilder;
Map<String,String> _variables;
@Inject
public DefaultJsValidator(JsEvaluator jsEvaluator) {
this.jsEvaluator = jsEvaluator;
codeBuilder = new StringTemplateBuilder();
restoreBuilder = new StringTemplateBuilder("__", "__");
_variables = new HashMap<>();
}
@Override
public String validate(String jsTemplate, Collection<String> allowedVariables) {
// Ban some JS keywords that could lead to potential XSS attacks
for (String keyword : _jsMalicious) {
int idx = jsTemplate.toLowerCase().indexOf(keyword.toLowerCase());
if (idx >= 0) {
int end = jsTemplate.indexOf("\n", idx);
end = (end != -1 && end-idx < 30) ? end : idx + 30;
String expr = jsTemplate.substring(idx, end >= jsTemplate.length() ? jsTemplate.length() : end);
return SourceCodeValidatorConstants.INSTANCE.js_keyword_not_allowed(expr);
}
}
try {
// Ensure all the variables in the template match the allowed ones
codeBuilder.setTemplate(jsTemplate);
for (String key : codeBuilder.keys()) {
String var = codeBuilder.asVar(key);
if (allowedVariables != null && !allowedVariables.contains(var)) {
return SourceCodeValidatorConstants.INSTANCE.js_variable_not_found(var);
}
}
// Mock the variables in the template and evaluate the whole script
String js = replaceVariables(jsTemplate);
jsEvaluator.evaluate(js);
// Evaluate each line individually
js = isolateLines(js);
jsEvaluator.evaluate(js);
return null;
}
catch (Exception e) {
// Replace back the original variables into the error message
String error = e.getMessage();
return restoreVariables(error);
}
}
public String replaceVariables(String code) {
StringBuilder header = new StringBuilder();
header.append("function __alert(msg) {};\n");
codeBuilder.setTemplate(code.replace("alert", "__alert"));
_variables.clear();
int idx = 0;
for (String key : codeBuilder.keys()) {
String var = "var" + idx++;
_variables.put(var, key);
header.append("var __" + var + "__ = document.createElement(\"div\");\n");
codeBuilder.replace(key, "__" + var + "__");
}
String body = codeBuilder.build();
return header + body;
}
public String restoreVariables(String code) {
restoreBuilder.setTemplate(code);
for (String var : _variables.keySet()) {
restoreBuilder.replace(var, codeBuilder.getKeyPrefix() + _variables.get(var) + codeBuilder.getKeySufix());
}
return restoreBuilder.build();
}
public String isolateLines(String code) {
StringBuilder out = new StringBuilder();
String[] lines = code.split("\n");
for (String line : lines) {
line = line.trim();
line = line.contains("else ") ? line.replace("else ", "") : line;
if (line.startsWith("{") && !line.endsWith("}") && occurrences(line, "{") > occurrences(line, "}")) {
line = line.substring(1);
}
if (!line.startsWith("{") && line.endsWith("}") && occurrences(line, "{") < occurrences(line, "}")) {
line = line.substring(0, line.length()-1);
}
if (line.endsWith("{")) {
line = line + "}";
}
if (line.startsWith("}")) {
line = "{" + line;
}
if (line.equals("") || line.equals("{}") || line.equals("{};")) {
continue;
}
out.append(line).append("\n");
}
return out.toString();
}
public int occurrences(String str, String target) {
int idx = 0;
int count = 0;
while (idx != -1 && idx < str.length()) {
idx = str.indexOf(target, idx);
if (idx != -1) {
count++;
idx += target.length();
}
}
return count;
}
}