* Примечание: мнения, выраженные в этой записи блога, являются моими и не обязательно отражают точку зрения моего работодателя. *
Теперь, когда уведомление работодателя не соответствует моему способу, я действительно могу начать говорить о том, о чем я хочу поговорить. На работе у нас есть большая инициатива, чтобы поместить 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, поэтому я начал с нового проекта Groovy в Eclipse. Затем я добавил следующие JAR-файлы в мой classpath:
- подпапке
- Обще-каротаж 1.1.jar
- dom4j-1.6.1.jar
- пои-3,7-beta3-20100924.jar
- пои-OOXML-3,7-beta3-20100924.jar
- пои-OOXML-схемы-3,7-beta3-20100924.jar
- XMLBeans-2.3.0.jar
Эти JAR-файлы дают нам проект Apache POI для создания документов Excel, а также необходимые классы ANT для создания пользовательской задачи ANT. Затем я создал новый пакет в своем недавно созданном проекте и назвал его ** com.apihealthcare.opt **. В этом пакете я создал новый класс Groovy с именем
QueryParamChecker . Первое, что необходимо для создания пользовательской задачи ANT, это импортировать классы Apache ANT, а затем расширить класс Task. Ваш новый класс должен переопределить метод
execute () и предоставить
сеттеры для каждого свойства, которое будет поддерживать ваша новая задача.
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_(.*)/ ]
Отсюда я создал две функции. Первый проверит один файл на наличие ошибок в регулярных выражениях, а второй — рекурсивную структуру каталогов. Они оба очень похожи и, вероятно, могли бы быть написаны более многократно, но пока они делают свое дело. По сути, эти функции будут читать текст из файла и сравнивать его с каждым регулярным выражением в массиве ** проверок **. Если есть совпадения, они сохраняются в структуре и помещаются в массив ** badCodeResults **.
После этого вызывается метод ** _ writeOutputFile () **, чтобы взять элементы в ** badCodeResults ** и поместить их в электронную таблицу Excel. Для этого вы сначала создаете объект ** XSSFWorkbook **, ** XSSFCreationHelper ** и ** XSSFSheet **. Лист создается из рабочей книги; по сути, он создает новый лист в книге в Excel. Я зацикливаюсь на ** badCodeResults ** и создаю ячейки для пути к файлу, поврежденного текста, а также начального и конечного местоположений поврежденного текстового местоположения. В довершение я записываю файл на диск.
Ниже приведена задача в полном объеме. Вы также можете
скачать полный исходный код . Удачного кодирования!
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}"
}
}