All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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