Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/main/java/com/cflint/plugins/core/ComponentPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.cflint.plugins.core;

import org.apache.tools.ant.taskdefs.Java;

import java.awt.*;
import java.io.File;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.io.IOException;

public class ComponentPath {
private static HashMap<String, HashSet<String>> componentsMapCache;
private Path rootPath;
private static ComponentPath single_instance ;
private String separator;
private static HashSet<String> CFknownScriptFunctions;

public static ComponentPath getInstance(String folder) {
if (single_instance == null)
single_instance = new ComponentPath(folder);

return single_instance;
}

private ComponentPath(String folderPath) {
//work around to find web root
// initialize known sub folders of web root
//without this, to find the web root , we have to search for server/web.xml coldfusion configuration file
String[] knownRootSubDirs = {"api", "voucherengine", "components", "avisfuelcard", "commandchain", "ioc", "parrot", "paycard", "santam", "voucherpos", "access", "customTags", "common"};
String strlist = System.getProperty("knownsubdirs");
if (strlist != null && !strlist.isEmpty()) {
knownRootSubDirs = strlist.split(",");
}
HashSet<String> knownRootSubs = new HashSet<>(Arrays.asList(knownRootSubDirs));
File current = Paths.get(folderPath).toAbsolutePath().normalize().toFile();
boolean foundRoot = false;
System.out.println("searching web root from " + current.toString());
do {
String currentDir = current.getParentFile().getName();
if (knownRootSubs.contains(currentDir.toLowerCase())) {
rootPath = current.getParentFile().getParentFile().toPath();
foundRoot = true;
System.out.println("found web root" + rootPath.toString());
}
current = current.getParentFile();
} while (!foundRoot && current.toPath().getNameCount() != 0);

componentsMapCache = new HashMap<>();
separator = System.getProperty("file.separator");

String[] knownScriptFuncs = {"ftp", "http", "mail", "pdf", "query", "storedproc", "dbinfo", "imap", "pop", "ldap", "feed"};
CFknownScriptFunctions = new HashSet<>(Arrays.asList(knownScriptFuncs));

}

public boolean ComponentExists(String componentName, String currentSrcFile) {

if (isKnownCFScriptFunc(componentName)) return true;
if (rootPath == null) {
System.out.println("root path not found");
} else {
Path compPath = getComponentPath(componentName, currentSrcFile);
HashSet<String> currentDirFiles = initOrGetFileNamesFromCache(compPath);
if (currentDirFiles.contains(compPath.toAbsolutePath().toString())) {
return true;
}
}
return false;
}

public boolean isKnownCFScriptFunc(String componentName) {
if (CFknownScriptFunctions.contains(componentName.toLowerCase())) {
return true;
}
return false;
}

private Path getComponentPath(String componentName, String currentSrcFile) {
String folderpath = "";
if (componentName.contains(".")) {
folderpath = rootPath.toAbsolutePath() + separator + String.join(separator, componentName.split("\\.")) + ".cfc";
} else {
Path parentDir = Paths.get(currentSrcFile).getParent();
folderpath = parentDir.toAbsolutePath().normalize().toString() + separator + componentName + ".cfc";
}
return Paths.get(folderpath);

}

private HashSet<String> initOrGetFileNamesFromCache(Path current) {
File componentFile = current.toAbsolutePath().toFile();
if (componentFile.isFile()) {
current = current.getParent();
}
if (!componentsMapCache.containsKey(current)) {
componentsMapCache.put(current.toString(), new HashSet<>());
} else {
return componentsMapCache.get(current.toString());
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(current, "*.{cfc,cfm}")) {
for (Path entry : stream) {
componentsMapCache.get(current.toString()).add(entry.toString());
}
} catch (IOException ex) {
System.out.println(ex.toString());
}
return componentsMapCache.get(current.toString());
}
}
154 changes: 154 additions & 0 deletions src/main/java/com/cflint/plugins/core/ComponentUseChecker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.cflint.plugins.core;

import cfml.parsing.cfscript.CFExpression;
import cfml.parsing.cfscript.script.*;
import com.cflint.BugList;
import com.cflint.CF;
import com.cflint.plugins.CFLintScannerAdapter;
import com.cflint.plugins.Context;

import net.htmlparser.jericho.Element;
import ro.fortsoft.pf4j.Extension;

import java.io.Console;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.HashMap;

import com.cflint.tools.CFTool;

@Extension
public class ComponentUseChecker extends CFLintScannerAdapter {

public enum UsageTypes {
NEW_COMPONENT,
CREATEOBJECT,
ATTRIBUTE,
CFML_TEMPLATE,
DI_GET_INSTANCE,
UNDEFINED
}

;
static final HashMap<UsageTypes, Pattern> patternsMap;
static final Pattern PascalCasePattern;

static {
patternsMap = new HashMap<>();
patternsMap.put(UsageTypes.CFML_TEMPLATE, Pattern.compile("template=\"(.*)\""));
patternsMap.put(UsageTypes.CREATEOBJECT, Pattern.compile("createObject.*component[\\'\\\"\\,\\s]+[\\'\\\"]([\\w\\d.]*)[\\'\\\"]\\s*\\)"));
patternsMap.put(UsageTypes.ATTRIBUTE, Pattern.compile("[\\'\\\"]([\\w\\.]+)[\\'\\\"]"));
patternsMap.put(UsageTypes.NEW_COMPONENT, Pattern.compile("new\\s+([\\w\\d.]*)\\(.*"));
patternsMap.put(UsageTypes.DI_GET_INSTANCE, Pattern.compile("server\\.di\\.getInstance\\([\\s\\'\\\"]([\\w\\d.]*)[\\'\\\"]\\s*\\)"));
PascalCasePattern = Pattern.compile("^[A-Z][a-z]+(?:[A-Z][a-z]+)*$");
}

@Override
public void expression(final CFScriptStatement expression, final Context context, final BugList bugs) {
if (expression instanceof CFExpressionStatement) {
final String code = ((CFExpressionStatement) expression).getExpression().Decompile(0);
final int lineNo = expression.getLine() + context.startLine() - 1;
final int offset = expression.getOffset() + context.offset();
checkComponentUse(context, code, lineNo, offset);
} else if (expression instanceof CFCompDeclStatement) {
CFCompDeclStatement compDeclStatement = ((CFCompDeclStatement) expression);
Map<String, CFExpression> attributes = CFTool.convertMap(compDeclStatement.getAttributes());
CFExpression extendsOrImplements = null;
if (attributes.containsKey("extends")) {
extendsOrImplements = attributes.get("extends");
} else if (attributes.containsKey("implements"))
extendsOrImplements = attributes.get("implements");
if (extendsOrImplements != null) {
final int lineNo = extendsOrImplements.getLine() + context.startLine() - 1;
final int offset = compDeclStatement.getOffset() + context.offset();
checkComponentUse(context, extendsOrImplements.Decompile(0), lineNo, offset, UsageTypes.ATTRIBUTE);
}
} else if (expression instanceof CFPropertyStatement) {
CFPropertyStatement propStatement = (CFPropertyStatement) expression;
Map<String, CFExpression> attributes = CFTool.convertMap(propStatement.getAttributes());
CFExpression injectAttribute = null;
if (attributes.containsKey("inject")) {
injectAttribute = attributes.get("inject");
}
if (injectAttribute != null) {
final int lineNo = injectAttribute.getLine() + context.startLine() - 1;
final int offset = injectAttribute.getOffset() + context.offset();
checkComponentUse(context, injectAttribute.Decompile(0), lineNo, offset, UsageTypes.ATTRIBUTE);
}
}

}

@Override
public void element(final Element element, final Context context, final BugList bugs) {
if (element.getName().equals(CF.CFCOMPONENT)) {
final String attributeVal = element.getAttributeValue("extends");
if (attributeVal != null && !attributeVal.trim().isEmpty()) {
final int lineNo = element.getSource().getRow(element.getBegin());
verifyComponentUsage(context, lineNo, element.getBegin(), attributeVal);
}
} else if (element.getName().equals(CF.CFSET)) {
final String content = element.getStartTag().getTagContent().toString();
final int lineNo = element.getSource().getRow(element.getBegin());
checkComponentUse(context, content, lineNo, element.getBegin());
}
}


private void checkComponentUse(Context context, String code, int lineNo, int offset) {
UsageTypes componentUsage = getComponentUseIfAny(code);
if (patternsMap.containsKey(componentUsage)) {
checkComponentUse(context, code, lineNo, offset, componentUsage);
}
}

private void checkComponentUse(Context context, String code, int lineNo, int offset, UsageTypes componentUsage) {
Matcher matcher = patternsMap.get(componentUsage).matcher(code.replace("\n", "").replace("\r", ""));
if (matcher.find()) {
String componentName = matcher.group(1);
verifyComponentUsage(context, lineNo, offset, componentName);
}
}

private void verifyComponentUsage(Context context, int lineNo, int offset, String componentName) {
String fileName = context.getFilename();
System.out.println("checking " + fileName);
System.out.println("looking for component: " + componentName);
ComponentPath compPath = ComponentPath.getInstance(fileName);
if (!compPath.ComponentExists(componentName, fileName)) {
context.addMessage("COMPONENT_NOT_FOUND", componentName, lineNo, offset);
} else {
if (componentName.contains(".")) {
String[] paths = componentName.split("\\.");
componentName = paths[paths.length - 1];
}
if (!PascalCasePattern.matcher(componentName).matches()) {
context.addMessage("INVALID_COMPONENT_USAGE", componentName, lineNo, offset);
}
}

}

protected UsageTypes getComponentUseIfAny(final String code) {
String codeLine = code.replaceAll("\\s+", "").toLowerCase();
if (codeLine.contains("createobject")) {
return UsageTypes.CREATEOBJECT;
} else if (codeLine.contains("new")) {
return UsageTypes.NEW_COMPONENT;
} else if (codeLine.contains("server.di.getinstance")) {
return UsageTypes.DI_GET_INSTANCE;
} else if (codeLine.contains("extends")) {
return UsageTypes.ATTRIBUTE;
} else if (codeLine.contains("implements")) {
return UsageTypes.ATTRIBUTE;
} else if (codeLine.contains("inject")) {
return UsageTypes.ATTRIBUTE;
} else if (codeLine.contains("template")) {
return UsageTypes.CFML_TEMPLATE;
} else {
return UsageTypes.UNDEFINED;
}
}

}
22 changes: 22 additions & 0 deletions src/main/resources/cflint.definition.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
{
"ruleImpl": [
{
"name": "ComponentUseChecker",
"className": "ComponentUseChecker",
"message": [
{
"code": "INVALID_COMPONENT_USAGE",
"severity": "ERROR",
"messageText": "Component '${variable}' usage is not valid . Please use ${case}"
},
{
"code": "COMPONENT_NOT_FOUND",
"severity": "INFO",
"messageText": "Component '${variable}' not found."
}
],
"parameter": [
{
"name": "case",
"value": "PascalCase"
}
]
},
{
"name": "ArgDefChecker",
"message": [
Expand Down
Loading