*注意:在此博客文章中所表达的意见仅限于我个人,并不代表我的雇主的观点。*
既然雇主的通知不在我的视线范围内,我就可以开始谈论我想要谈论的事情了。在工作中,我们有一个很大的倡议,就是将CFQUERYPARAM放入没有CFQUERYPARAM的SQL语句中。由于这段代码相当老旧,其中大部分都没有使用这个方便的标记,因此正如您所想象的那样,要查看3000多个文件并验证CFQUERYPARAM是否被放到了需要的任何地方,这是一项相当大的任务。
在最初的工作之后,我的一位同事创建了一系列正则表达式来帮助验证工作,并指出我们可能做错了工作的任何地方。老实说,盯着屏幕看8个小时,寻找CFQUERY和英镑符号中的变量,可能很快就会变得模糊。如果有这些有用的正则表达式,我想最好有一个简单的ANT脚本,它会提示输入目录或文件名资源,并针对该资源运行这些正则表达式,并在Excel电子表格中创建任何潜在问题区域的报告。
为了演示,让我们看看ANT脚本本身是什么样子的。
<!--?xml version="1.0"?-->
<project name="CFQUERYPARAM Tester" default="main" basedir=".">
<taskdef name="queryParamChecker" classname="com.apihealthcare.opt.QueryParamChecker">
<target name="main">
<input message="Please provide a directory path or file path to scan:" addproperty="path" defaultvalue="${basedir}">
<queryparamchecker path="${path}" outputfile="C:\\anttasktestresults.xlsx">
</queryparamchecker></target>
</taskdef></project>
如您所见,要做的第一件事是“导入”一个名为
QueryParamChecker。这是我编写的一个自定义ANT任务,它基于一组正则表达式扫描资源以查找潜在问题。然后我们使用
输入任务提示用户输入资源名称,目录或文件路径,然后将其传递给查询参数检查器任务。
那么如何编写自定义ANT任务呢?为此,我使用了Groovy,因此我从Eclipse中的一个新Groovy项目开始。然后,我将以下JAR文件添加到类路径中:
- 蚂蚁罐子
- commons-logging-1.1.jar
- dom4j-1.6.1.jar
- poi-3.7-beta3-20100924.jar
- poi-ooxml-3.7-beta3-20100924.jar
- poi-ooxml-schemas-3.7-beta3-20100924.jar
- xmlbeans-2.3.0.jar
这些JAR文件为我们提供了用于创建Excel文档的Apache POI项目,以及构建自定义ANT任务所必需的ANT类。然后,我在新创建的项目中创建了一个新包,并将其称为**com.apiHealthCare.opt**。在这个包中,我创建了一个新的Groovy类,名为
QueryParamChecker。创建自定义ANT任务所必需的第一件事是导入Apache ANT类,然后扩展task类。新类必须重写
execute()方法,并提供
新任务将支持的每个属性的setter。
package com.apihealthcare.opt
import org.apache.tools.ant.*
class QueryParamChecker extends Task
{
private String path
private String outputFile
@Override
public void execute() throws BuildException {
}
public void setPath(String path) {
this.path = path
}
public void setOutputFile(String outputFile) {
this.outputFile = outputFile
}
}
这是定制ANT任务的框架。但我想要的显然不只是一具骷髅。我需要它来检查资源中的CFQueryParam错误。有三条我用来做这件事的信息。第一个是一个正则表达式对象数组,它是我们这里要做的主要内容。接下来是一个文件扩展名数组,我们可以检查这些扩展名,因此它包含“。cfm”和“。cfc”。最后,我有一个正则表达式数组,用于过滤所有不需要的文件或文件夹,因为这个应用程序有许多不再使用的旧文件。这些是阵列。
/*
* An array of regular expressions to check files against.
* Modify this list to change the rules yo.
*/
private def checks = [
~/(?i)in\s*\(\s*<cfqueryparam((?!list).)*>/,
//~/(?i)in\s*\(\s*<cfqueryparam((?!cfsqltype).)*>/,
~/(?i)(#\s*cfsqltype=|#\s*maxlength=|#\s*list=|#\s*value=)/,
~/(?i)\sin(\s|\()[^>]*(value="#listqualify|value="#replace|value="#preservesinglequotes)/,
~/(?i)[^<]cfqueryparam/,
~/(?i)<cfqueryparam[^<>]*"\s*\/[^>]/,
~/(?i)<cfqueryparam\s*value=#/,
~/(?i)value="#dateadd/,
~/(?i)<cfqueryparams/,
~/(?i)cfsqltype=""/,
~/(?i)(#"list|#"value|#"cfsqltype|#"maxlength)/,
~/(?i)order\s*by\s*<cfqueryparam/,
~/(?i)(cfqueryparamvalue|cfqueryparamcfsqltype|cfqueryparammaxlength|cfqueryparamlist)/,
~/(?i)<cfqueryparam[^>]*(?=\/\s*"\s*>)/,
~/(?i)charindex\([^\)]*<cfqueryparam/,
~/(?i)[^<!-|<!]--[^>|-].*<cfqueryparam/,
~/(?i)session\.(?!(hasPermission|get|usersession|set).*)/
]
/*
* An array of extensions that we care about. Ignore all else.
*/
private def validExtensions = [
".cfm",
".cfc"
]
/*
* File name regex patterns to ignore.
*/
private def ignores = [
~/(?i)(.*?)unused_(.*)/
]
从这里我创建了两个函数。第一种方法将根据正则表达式检查单个文件的错误,第二种方法将递归一个目录结构。它们都非常相似,而且很可能被编写得更可重用,但目前它们完成了任务。本质上,这些函数将从文件中读取文本,并将其与**checks**数组中的每个正则表达式进行比较。如果存在匹配项,则将它们存储在一个结构中,并放入**BadCodeResults**数组中。
完成这些操作后,调用**_WriteOutputFile()**方法来获取**BadCodeResults**中的项,并将它们放入Excel电子表格中。为此,您首先创建一个**XSSFWorkbook**,一个**XSSFCreationHelper**和一个**XSSFSheet**对象。工作表是从工作簿中创建的;实际上,它在Excel的工作簿中创建一个新的工作表。I在**BadCodeResults**上循环,并为文件路径,违规文本以及违规文本位置的开始和结束位置创建单元格。然后将文件写入磁盘。
下面是整个任务。您还可以
download the full source code。快乐的编码!
package com.apihealthcare.opt
import org.apache.tools.ant.*
import groovy.io.FileType
import org.apache.poi.poifs.filesystem.*
import org.apache.poi.xssf.extractor.*
import org.apache.poi.xssf.usermodel.*
class QueryParamChecker extends Task
{
private String path
private String outputFile
/*
* An array of regular expressions to check files against.
* Modify this list to change the rules yo.
*/
private def checks = [
~/(?i)in\s*\(\s*<cfqueryparam((?!list).)*>/,
//~/(?i)in\s*\(\s*<cfqueryparam((?!cfsqltype).)*>/,
~/(?i)(#\s*cfsqltype=|#\s*maxlength=|#\s*list=|#\s*value=)/,
~/(?i)\sin(\s|\()[^>]*(value="#listqualify|value="#replace|value="#preservesinglequotes)/,
~/(?i)[^<]cfqueryparam/,
~/(?i)<cfqueryparam[^<>]*"\s*\/[^>]/,
~/(?i)<cfqueryparam\s*value=#/,
~/(?i)value="#dateadd/,
~/(?i)<cfqueryparams/,
~/(?i)cfsqltype=""/,
~/(?i)(#"list|#"value|#"cfsqltype|#"maxlength)/,
~/(?i)order\s*by\s*<cfqueryparam/,
~/(?i)(cfqueryparamvalue|cfqueryparamcfsqltype|cfqueryparammaxlength|cfqueryparamlist)/,
~/(?i)<cfqueryparam[^>]*(?=\/\s*"\s*>)/,
~/(?i)charindex\([^\)]*|-].*<cfqueryparam/,
~/(?i)session\.(?!(hasPermission|get|usersession|set).*)/
]
/*
* An array of extensions that we care about. Ignore all else.
*/
private def validExtensions = [
".cfm",
".cfc"
]
/*
* File name regex patterns to ignore.
*/
private def ignores = [
~/(?i)(.*?)unused_(.*)/
]
@Override
public void execute() throws BuildException {
def fileCheck = new File(this.path)
def result = []
if (fileCheck.isFile()) {
result = _doFile()
}
else if (fileCheck.isDirectory()) {
result = _doDirectory()
}
else
throw new Exception("The path passed in doesn't seem to be a file or a directory!")
_writeOutputFile(result)
}
public void setPath(String path) {
this.path = path
}
public void setOutputFile(String outputFile) {
this.outputFile = outputFile
}
private def _doFile() {
def badCodeResults = []
/*
* The directory we are searching goes here!
*/
def f = new File(this.path)
assert f.isFile()
def filesProcessed = 0
def badFiles = 0
def validFile = false
def printed = false
/*
* Do we care about this particular file? If not set the
* validFile flag to false.
*/
validExtensions.each {
if (f.name.endsWith(it)) validFile = true
}
ignores.each {
def ignoreMe = f.name ==~ it
if (validFile != false && ignoreMe) validFile = false
}
/*
* Enter here if we care.
*/
if (validFile) {
filesProcessed++
/*
* Start looping over each regex we wish to run against this file.
*/
checks.each { regex ->
def matcher = f.text =~ regex
def index = 0
/*
* Loop over any matches in the file.
*/
while (matcher.find()) {
/*
* We have a bad code match! Put it into our results array.
*/
if (matcher.group(0) != null && matcher.group(0) != "") {
if (!printed) {
println "File: ${f.name}..."
badFiles++
}
printed = true
badCodeResults << [
filePath: f.getAbsolutePath(),
offendingText: matcher.group(0),
start: matcher.start(),
end: matcher.end()
]
}
}
}
}
println "Processed ${filesProcessed} file(s)"
println "${badFiles} bad file(s) found"
badCodeResults
}
private def _doDirectory() {
def badCodeResults = []
/*
* The directory we are searching goes here!
*/
def rootPath = this.path
def codeBase = new File(rootPath)
assert codeBase.isDirectory()
def filesProcessed = 0
def badFiles = 0
/*
* Iterate over all files in our source directory.
*/
codeBase.eachFileRecurse FileType.FILES, { f ->
def validFile = false
def printed = false
/*
* Do we care about this particular file? If not set the
* validFile flag to false.
*/
validExtensions.each {
if (f.name.endsWith(it)) validFile = true
}
ignores.each {
def ignoreMe = f.name ==~ it
if (validFile != false && ignoreMe) validFile = false
}
/*
* Enter here if we care.
*/
if (validFile) {
filesProcessed++
/*
* Start looping over each regex we wish to run against this file.
*/
checks.each { regex ->
def matcher = f.text =~ regex
def index = 0
/*
* Loop over any matches in the file.
*/
while (matcher.find()) {
/*
* We have a bad code match! Put it into our results array.
*/
if (matcher.group(0) != null && matcher.group(0) != "") {
if (!printed) {
println "File: ${f.name}..."
badFiles++
}
printed = true
badCodeResults << [
filePath: f.getAbsolutePath() - rootPath,
offendingText: matcher.group(0),
start: matcher.start(),
end: matcher.end()
]
}
}
}
}
println "Processed ${filesProcessed} file(s)"
println "${badFiles} bad file(s) found"
badCodeResults
}
private def _writeOutputFile(badCodeResults) {
/*
* Create a workbook and worksheet.
*/
XSSFWorkbook wb = new XSSFWorkbook()
XSSFCreationHelper helper = wb.getCreationHelper()
XSSFSheet sheet = wb.createSheet("Search Results")
def rowIndex = 0
/*
* Loop over all our bad code results and write them to
* rows in the Excel sheet.
*/
badCodeResults.each {
XSSFRow row = sheet.createRow(rowIndex++)
row.createCell(0).setCellValue(helper.createRichTextString(it.filePath))
row.createCell(1).setCellValue(helper.createRichTextString(it.offendingText))
row.createCell(2).setCellValue(it.start)
row.createCell(3).setCellValue(it.end)
}
/*
* Write out the results file. Note the path.
*/
FileOutputStream out = new FileOutputStream(this.outputFile)
wb.write(out)
out.close()
println "Output results written to ${this.outputFile}"
}
}