
com.liferay.source.formatter.JSPSourceProcessor Maven / Gradle / Ivy
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.source.formatter;
import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.ReflectionUtil;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.TextFormatter;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.tools.ImportsFormatter;
import com.liferay.portal.tools.JavaImportsFormatter;
import com.liferay.portal.tools.ToolsUtil;
import com.liferay.source.formatter.util.FileUtil;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.Type;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Hugo Huijser
*/
public class JSPSourceProcessor extends BaseSourceProcessor {
@Override
public String[] getIncludes() {
return _INCLUDES;
}
protected void addImportCounts(String content) {
Matcher matcher = _importsPattern.matcher(content);
while (matcher.find()) {
String importName = matcher.group(1);
int count = 0;
if (_importCountMap.containsKey(importName)) {
count = _importCountMap.get(importName);
}
else {
int pos = importName.lastIndexOf(CharPool.PERIOD);
String importClassName = importName.substring(pos + 1);
if (_importClassNames.contains(importClassName)) {
_duplicateImportClassNames.add(importClassName);
}
else {
_importClassNames.add(importClassName);
}
}
_importCountMap.put(importName, count + 1);
}
}
protected List addIncludedAndReferencedFileNames(
List fileNames, Set checkedFileNames) {
Set includedAndReferencedFileNames = new HashSet<>();
for (String fileName : fileNames) {
if (!checkedFileNames.add(fileName)) {
continue;
}
fileName = StringUtil.replace(
fileName, StringPool.BACK_SLASH, StringPool.SLASH);
includedAndReferencedFileNames.addAll(
getJSPIncludeFileNames(fileName, fileNames));
includedAndReferencedFileNames.addAll(
getJSPReferenceFileNames(fileName, fileNames));
}
if (includedAndReferencedFileNames.isEmpty()) {
return fileNames;
}
for (String fileName : includedAndReferencedFileNames) {
fileName = StringUtil.replace(
fileName, StringPool.SLASH, StringPool.BACK_SLASH);
if (!fileNames.contains(fileName)) {
fileNames.add(fileName);
}
}
return addIncludedAndReferencedFileNames(fileNames, checkedFileNames);
}
protected void addJSPUnusedImports(
String fileName, List importLines,
List unneededImports) {
for (String importLine : importLines) {
int x = importLine.indexOf(CharPool.QUOTE);
int y = importLine.indexOf(CharPool.QUOTE, x + 1);
if ((x == -1) || (y == -1)) {
continue;
}
String className = importLine.substring(x + 1, y);
className = className.substring(
className.lastIndexOf(CharPool.PERIOD) + 1);
String regex = "[^A-Za-z0-9_\"]" + className + "[^A-Za-z0-9_\"]";
if (hasUnusedJSPTerm(fileName, regex, "class")) {
unneededImports.add(importLine);
}
}
}
protected String buildFullPathIncludeFileName(
String fileName, String includeFileName) {
String path = fileName;
while (true) {
int y = path.lastIndexOf(CharPool.SLASH);
if (y == -1) {
return StringPool.BLANK;
}
String fullPathIncludeFileName =
path.substring(0, y) + includeFileName;
if (_jspContents.containsKey(fullPathIncludeFileName) &&
!fullPathIncludeFileName.equals(fileName)) {
return fullPathIncludeFileName;
}
path = path.substring(0, y);
}
}
protected boolean checkTaglibVulnerability(
String jspContent, String vulnerability) {
int pos1 = -1;
do {
pos1 = jspContent.indexOf(vulnerability, pos1 + 1);
if (pos1 != -1) {
int pos2 = jspContent.lastIndexOf(CharPool.LESS_THAN, pos1);
while ((pos2 > 0) &&
(jspContent.charAt(pos2 + 1) == CharPool.PERCENT)) {
pos2 = jspContent.lastIndexOf(CharPool.LESS_THAN, pos2 - 1);
}
String tagContent = jspContent.substring(pos2, pos1);
if (!tagContent.startsWith("";
if (checkTaglibVulnerability(jspContent, anchorVulnerability)) {
xssVulnerable = true;
}
String inputVulnerability = " value=\"<%= " + jspVariable + " %>";
if (checkTaglibVulnerability(jspContent, inputVulnerability)) {
xssVulnerable = true;
}
String inlineStringVulnerability1 = "'<%= " + jspVariable + " %>";
if (jspContent.contains(inlineStringVulnerability1)) {
xssVulnerable = true;
}
String inlineStringVulnerability2 = "(\"<%= " + jspVariable + " %>";
if (jspContent.contains(inlineStringVulnerability2)) {
xssVulnerable = true;
}
String inlineStringVulnerability3 = " \"<%= " + jspVariable + " %>";
if (jspContent.contains(inlineStringVulnerability3)) {
xssVulnerable = true;
}
String documentIdVulnerability = ".<%= " + jspVariable + " %>";
if (jspContent.contains(documentIdVulnerability)) {
xssVulnerable = true;
}
if (xssVulnerable) {
processErrorMessage(
fileName, "(xss): " + fileName + " (" + jspVariable + ")");
}
}
}
protected String compressImportsOrTaglibs(
String fileName, String content, String attributePrefix) {
if (!fileName.endsWith("init.jsp") && !fileName.endsWith("init.jspf")) {
return content;
}
int x = content.indexOf(attributePrefix);
int y = content.lastIndexOf(attributePrefix);
y = content.indexOf("%>", y);
if ((x == -1) || (y == -1) || (x > y)) {
return content;
}
String importsOrTaglibs = content.substring(x, y);
importsOrTaglibs = StringUtil.replace(
importsOrTaglibs, new String[] {"%>\r\n<%@ ", "%>\n<%@ "},
new String[] {"%><%@\r\n", "%><%@\n"});
return content.substring(0, x) + importsOrTaglibs +
content.substring(y);
}
protected String fixEmptyLineInNestedTags(
String content, Pattern pattern, boolean startTag) {
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
String tabs1 = matcher.group(1);
String tabs2 = matcher.group(2);
if ((startTag && ((tabs1.length() + 1) == tabs2.length())) ||
(!startTag && ((tabs1.length() - 1) == tabs2.length()))) {
content = StringUtil.replaceFirst(
content, StringPool.NEW_LINE, StringPool.BLANK,
matcher.end(1));
}
}
return content;
}
@Override
protected String doFormat(
File file, String fileName, String absolutePath, String content)
throws Exception {
String newContent = formatJSP(fileName, absolutePath, content);
newContent = StringUtil.replace(
newContent,
new String[] {
"
", "\"/>", "\" >", ">'/>", ">' >", "@page import", "\"%>",
")%>", "function (", "javascript: ", "){\n", ";;\n", "\n\n\n"
},
new String[] {
"
", "\" />", "\">", ">' />", ">'>", "@ page import",
"\" %>", ") %>", "function(", "javascript:", ") {\n", ";\n",
"\n\n"
});
newContent = fixRedirectBackURL(newContent);
newContent = fixCompatClassImports(absolutePath, newContent);
newContent = fixEmptyLineInNestedTags(
newContent, _emptyLineInNestedTagsPattern1, true);
newContent = fixEmptyLineInNestedTags(
newContent, _emptyLineInNestedTagsPattern2, false);
newContent = fixEmptyLineInNestedTags(
newContent, _emptyLineInNestedTagsPattern3, false);
if (_stripJSPImports && !_jspContents.isEmpty()) {
try {
newContent = formatJSPImportsOrTaglibs(
fileName, newContent, _jspImportPattern, true);
newContent = formatJSPImportsOrTaglibs(
fileName, newContent, _jspTaglibPattern, false);
}
catch (RuntimeException re) {
_stripJSPImports = false;
}
}
if (portalSource && content.contains("page import=") &&
!fileName.contains("init.jsp") &&
!fileName.contains("init-ext.jsp") &&
!fileName.contains("/taglib/aui/") &&
!fileName.endsWith("touch.jsp") &&
(fileName.endsWith(".jspf") || content.contains("include file="))) {
processErrorMessage(
fileName, "move imports to init.jsp: " + fileName);
}
newContent = fixCopyright(newContent, absolutePath, fileName);
newContent = StringUtil.replace(
newContent,
new String[] {
"alert('<%= LanguageUtil.", "alert(\"<%= LanguageUtil.",
"confirm('<%= LanguageUtil.", "confirm(\"<%= LanguageUtil."
},
new String[] {
"alert('<%= UnicodeLanguageUtil.",
"alert(\"<%= UnicodeLanguageUtil.",
"confirm('<%= UnicodeLanguageUtil.",
"confirm(\"<%= UnicodeLanguageUtil."
});
if (newContent.contains(" ")) {
if (!fileName.matches(".*template.*\\.vm$")) {
processErrorMessage(fileName, "tab: " + fileName);
}
}
newContent = compressImportsOrTaglibs(
fileName, newContent, "<%@ page import=");
newContent = compressImportsOrTaglibs(
fileName, newContent, "<%@ taglib uri=");
newContent = fixSessionKey(fileName, newContent, sessionKeyPattern);
newContent = fixSessionKey(
fileName, newContent, taglibSessionKeyPattern);
checkLanguageKeys(fileName, newContent, languageKeyPattern);
checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern1);
checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern2);
checkLanguageKeys(fileName, newContent, _taglibLanguageKeyPattern3);
checkXSS(fileName, newContent);
// LPS-47682
newContent = fixIncorrectParameterTypeForLanguageUtil(
newContent, true, fileName);
// LPS-48156
newContent = checkPrincipalException(newContent);
newContent = formatLogFileName(absolutePath, newContent);
// LPS-59076
if (portalSource && isModulesFile(absolutePath) &&
newContent.contains("import=\"com.liferay.registry.Registry")) {
processErrorMessage(
fileName, "Do not use Registry in modules: " + fileName);
}
Matcher matcher = _javaClassPattern.matcher(newContent);
if (matcher.find()) {
String javaClassContent = matcher.group();
javaClassContent = javaClassContent.substring(1);
String javaClassName = matcher.group(2);
String beforeJavaClass = newContent.substring(
0, matcher.start() + 1);
int javaClassLineCount =
StringUtil.count(beforeJavaClass, "\n") + 1;
newContent = formatJavaTerms(
javaClassName, null, file, fileName, absolutePath, newContent,
javaClassContent, javaClassLineCount, null, null, null, null);
}
if (!content.equals(newContent)) {
_jspContents.put(fileName, newContent);
}
return newContent;
}
@Override
protected List doGetFileNames() throws Exception {
_moveFrequentlyUsedImportsToCommonInit = GetterUtil.getBoolean(
getProperty("move.frequently.used.imports.to.common.init"));
_unusedVariablesExclusionFiles = getPropertyList(
"jsp.unused.variables.excludes.files");
String[] excludes = new String[] {"**/null.jsp", "**/tools/**"};
List fileNames = getFileNames(excludes, getIncludes());
if (fileNames.isEmpty()) {
return fileNames;
}
List allFileNames = null;
if (sourceFormatterArgs.isFormatCurrentBranch() ||
sourceFormatterArgs.isFormatLatestAuthor() ||
sourceFormatterArgs.isFormatLocalChanges()) {
allFileNames = getFileNames(
sourceFormatterArgs.getBaseDirName(), null, excludes,
getIncludes());
}
else {
allFileNames = fileNames;
}
try {
Pattern pattern = Pattern.compile(
"\\s*@\\s*include\\s*file=['\"](.*)['\"]");
for (String fileName : allFileNames) {
File file = new File(fileName);
fileName = StringUtil.replace(
fileName, StringPool.BACK_SLASH, StringPool.SLASH);
String absolutePath = getAbsolutePath(file);
String content = FileUtil.read(file);
Matcher matcher = pattern.matcher(content);
String newContent = content;
while (matcher.find()) {
newContent = StringUtil.replaceFirst(
newContent, matcher.group(),
"@ include file=\"" + matcher.group(1) + "\"",
matcher.start());
}
processFormattedFile(file, fileName, content, newContent);
if (portalSource && _moveFrequentlyUsedImportsToCommonInit &&
fileName.endsWith("/init.jsp") &&
!isModulesFile(absolutePath) &&
!fileName.endsWith("/common/init.jsp")) {
addImportCounts(content);
}
_jspContents.put(fileName, newContent);
}
if (portalSource && _moveFrequentlyUsedImportsToCommonInit) {
moveFrequentlyUsedImportsToCommonInit(4);
}
}
catch (Exception e) {
ReflectionUtil.throwException(e);
}
if (!sourceFormatterArgs.isFormatCurrentBranch() &&
!sourceFormatterArgs.isFormatLatestAuthor() &&
!sourceFormatterArgs.isFormatLocalChanges()) {
return fileNames;
}
return addIncludedAndReferencedFileNames(
fileNames, new HashSet());
}
protected String fixRedirectBackURL(String content) {
Matcher matcher = _redirectBackURLPattern.matcher(content);
String newContent = content;
while (matcher.find()) {
newContent = StringUtil.replaceFirst(
newContent, matcher.group(),
matcher.group(1) + "\n\n" + matcher.group(2), matcher.start());
}
return newContent;
}
protected String formatJSP(
String fileName, String absolutePath, String content)
throws Exception {
StringBundler sb = new StringBundler();
String currentAttributeAndValue = null;
String previousAttribute = null;
String previousAttributeAndValue = null;
String tag = null;
String currentException = null;
String previousException = null;
boolean hasUnsortedExceptions = false;
try (UnsyncBufferedReader unsyncBufferedReader =
new UnsyncBufferedReader(new UnsyncStringReader(content))) {
_checkedForIncludesFileNames = new HashSet<>();
_includeFileNames = new HashSet<>();
int lineCount = 0;
String line = null;
String previousLine = StringPool.BLANK;
boolean readAttributes = false;
boolean javaSource = false;
while ((line = unsyncBufferedReader.readLine()) != null) {
lineCount++;
if (portalSource && hasUnusedTaglib(fileName, line)) {
continue;
}
if (!fileName.contains("jsonw") ||
!fileName.endsWith("action.jsp")) {
line = trimLine(line, false);
}
if (line.contains("")) {
javaSource = false;
}
if (javaSource || trimmedLine.contains("<%= ")) {
checkInefficientStringMethods(
line, fileName, absolutePath, lineCount);
}
if (javaSource) {
if (portalSource &&
!isExcludedFile(
_unusedVariablesExclusionFiles, absolutePath,
lineCount) &&
!_jspContents.isEmpty() &&
hasUnusedVariable(fileName, trimmedLine)) {
continue;
}
}
line = formatWhitespace(line, trimmedLine, javaSource);
// LPS-47179
if (line.contains(".sendRedirect(") &&
!fileName.endsWith("_jsp.jsp")) {
processErrorMessage(
fileName,
"Do not use sendRedirect in jsp: " + fileName + " " +
lineCount);
}
// LPS-55341
if (!javaSource) {
line = StringUtil.replace(
line, "LanguageUtil.get(locale,",
"LanguageUtil.get(request,");
}
// LPS-58529
checkResourceUtil(line, fileName, lineCount);
if (!fileName.endsWith("test.jsp") &&
line.contains("System.out.print")) {
processErrorMessage(
fileName,
"System.out.print: " + fileName + " " + lineCount);
}
if (!trimmedLine.equals("%>") && line.contains("%>") &&
!line.contains("--%>") && !line.contains(" %>")) {
line = StringUtil.replace(line, "%>", " %>");
}
if (line.contains("<%=") && !line.contains("<%= ")) {
line = StringUtil.replace(line, "<%=", "<%= ");
}
if (trimmedPreviousLine.equals("%>") &&
Validator.isNotNull(line) && !trimmedLine.equals("-->")) {
sb.append("\n");
}
else if (Validator.isNotNull(previousLine) &&
!trimmedPreviousLine.equals("
© 2015 - 2025 Weber Informatics LLC | Privacy Policy