package org.zaproxy.zap.model; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; import java.util.regex.Pattern; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConversionException; import org.apache.commons.lang.Validate; import org.apache.log4j.Logger; import org.zaproxy.zap.utils.LocaleUtils; import org.zaproxy.zap.utils.ZapXmlConfiguration; /** * Helper class that loads {@code Vulnerability} from a XML file for a given {@code Locale}. * * @see Vulnerability */ public class VulnerabilitiesLoader { private static final Logger logger = Logger.getLogger(VulnerabilitiesLoader.class); private final Path directory; private final String fileName; private final String fileExtension; /** * Constructs a {@code VulnerabilitiesLoader} that loads the resource XML files from the given {@code directory} with the * given {@code fileName} and {@code fileExtension}. * * @param directory the directory where the XML files are located * @param fileName the name of the XML files that contains the vulnerabilities * @param fileExtension the extension (with dot) of the XML files that contains the vulnerabilities * @throws IllegalArgumentException if {@code directory} is {@code null} or if {@code fileName} and {@code fileExtension} * are {@code null} or empty */ public VulnerabilitiesLoader(Path directory, String fileName, String fileExtension) { Validate.notNull(directory, "Parameter directory must not be null."); Validate.notEmpty(fileName, "Parameter fileName must not be null nor empty."); Validate.notEmpty(fileExtension, "Parameter fileExtension must not be null nor empty."); this.directory = directory; this.fileName = fileName; this.fileExtension = fileExtension; } /** * Returns an unmodifiable {@code List} of {@code Vulnerability}s for the given {@code locale}. * <p> * If there's no perfect match for the given {@code locale} the default will be returned, if available. The list will be * empty if an error occurs. * * @param locale the locale that will {@code Vulnerability}s will be loaded * @return an unmodifiable {@code List} of {@code Vulnerability}s for the given {@code locale} */ public List<Vulnerability> load(Locale locale) { List<String> filenames = getListOfVulnerabilitiesFiles(); for (Locale candidateLocale : getCandidateLocales(locale)) { String candidateFilename = createFilename(candidateLocale); if (filenames.contains(candidateFilename)) { if (logger.isDebugEnabled()) { logger.debug("loading vulnerabilities from " + candidateFilename + " for locale " + locale + "."); } List<Vulnerability> list = loadVulnerabilitiesFile(directory.resolve(candidateFilename)); if (list == null) { return Collections.emptyList(); } return Collections.unmodifiableList(list); } } return Collections.emptyList(); } /** * Returns a {@code List} of candidate {@code Locale}s for {@code fileName} and given {@code locale}. * * @param locale the locale for which the candidate locales will be generated * @return a {@code List} of candidate {@code Locale}s for the given locale * @see java.util.ResourceBundle.Control#getCandidateLocales(String, Locale) */ private List<Locale> getCandidateLocales(Locale locale) { return ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT).getCandidateLocales(fileName, locale); } /** * Returns the resource filename for the given {@code locale}. * <p> * The filename is composed by:<blockquote>{@code fileName} + "_" + {@code locale} + {@code fileExtension} </blockquote> * <p> * If the given {@code locale} is empty it's composed by: <blockquote>{@code fileName} + {@code fileExtension}</blockquote> * * @param locale the locale used to create the filename * @return the resource filename for the given {@code locale} */ private String createFilename(Locale locale) { StringBuilder resourceBuilder = new StringBuilder(fileName); String strLocale = locale.toString(); if (!strLocale.isEmpty()) { resourceBuilder.append('_').append(locale); } resourceBuilder.append(fileExtension); return resourceBuilder.toString(); } private List<Vulnerability> loadVulnerabilitiesFile(Path file) { ZapXmlConfiguration config; try { config = new ZapXmlConfiguration(file.toFile()); } catch (ConfigurationException e) { logger.error(e.getMessage(), e); return null; } String[] test; try { test = config.getStringArray("vuln_items"); } catch (ConversionException e) { logger.error(e.getMessage(), e); return null; } final int numberOfVulns = test.length; List<Vulnerability> tempVulns = new ArrayList<>(numberOfVulns); String name; List<String> references; for (String item : test) { name = "vuln_item_" + item; try { references = new ArrayList<>(Arrays.asList(config.getStringArray(name + ".reference"))); } catch (ConversionException e) { logger.error(e.getMessage(), e); references = new ArrayList<>(0); } Vulnerability v = new Vulnerability( item, config.getString(name + ".alert"), config.getString(name + ".desc"), config.getString(name + ".solution"), references); tempVulns.add(v); } return tempVulns; } /** * Returns a {@code List} of resources files with {@code fileName} and {@code fileExtension} contained in the * {@code directory}. * * @return the list of resources files contained in the {@code directory} * @see LocaleUtils#createResourceFilesPattern(String, String) */ private List<String> getListOfVulnerabilitiesFiles() { final Pattern filePattern = LocaleUtils.createResourceFilesPattern(fileName, fileExtension); final List<String> fileNames = new ArrayList<>(); try { Files.walkFileTree(directory, Collections.<FileVisitOption> emptySet(), 1, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String fileName = file.getFileName().toString(); if (filePattern.matcher(fileName).matches()) { fileNames.add(fileName); } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { logger.error("An error occurred while walking directory: " + directory, e); } return fileNames; } }