/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2012 ZAP development team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.zaproxy.zap.control; import java.io.File; import java.net.URL; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.zaproxy.zap.utils.ZapXmlConfiguration; public class AddOnCollection { public enum Platform {daily, windows, linux, mac} private static final Logger logger = Logger.getLogger(AddOnCollection.class); private ZapRelease zapRelease = null; private List <AddOn> addOns = new ArrayList<>(); private File downloadDir = new File(Constant.FOLDER_LOCAL_PLUGIN); private Platform platform; public AddOnCollection(ZapXmlConfiguration config, Platform platform) { this(config, platform, true); } public AddOnCollection(ZapXmlConfiguration config, Platform platform, boolean allowAddOnsWithDependencyIssues) { this.platform = platform; this.load(config); if (!allowAddOnsWithDependencyIssues) { List<AddOn> checkedAddOns = new ArrayList<>(addOns); List<AddOn> runnableAddOns = new ArrayList<>(addOns.size()); while (!checkedAddOns.isEmpty()) { AddOn addOn = checkedAddOns.remove(0); // Shouldn't happen but make sure to not show add-ons that wouldn't run, or one of its extensions // because of dependency issues or AddOn.AddOnRunRequirements requirements = addOn.calculateRunRequirements(addOns); if (requirements.hasDependencyIssue()) { if (logger.isDebugEnabled()) { logger.debug("Ignoring add-on " + addOn.getName() + " because of dependency issue: " + AddOnRunIssuesUtils.getDependencyIssue(requirements)); } if (AddOn.AddOnRunRequirements.DependencyIssue.CYCLIC == requirements.getDependencyIssue()) { @SuppressWarnings("unchecked") Set<AddOn> cyclicChain = (Set<AddOn>) requirements.getDependencyIssueDetails().get(0); checkedAddOns.removeAll(cyclicChain); } } else if (requirements.hasExtensionsWithRunningIssues()) { if (logger.isDebugEnabled()) { logger.debug("Ignoring add-on " + addOn.getName() + " because of dependency issue in an extension: " + AddOnRunIssuesUtils.getDependencyIssue(requirements)); } } else { runnableAddOns.add(addOn); } } addOns = runnableAddOns; } } private void load (ZapXmlConfiguration config) { config.setExpressionEngine(new XPathExpressionEngine()); try { // See if theres a ZAP release defined String version = config.getString("core/version"); if (Platform.daily.equals(platform)) { // Daily releases take precedence even if running on Kali as they will have been manually installed version = config.getString("core/daily-version", version); } else if (Constant.isKali()) { version = config.getString("core/kali-version", version); } if (version != null && version.length() > 0) { String relUrlStr = config.getString("core/relnotes-url", null); URL relUrl = null; if (relUrlStr != null ) { relUrl = new URL(relUrlStr); } this.zapRelease = new ZapRelease( version, new URL(config.getString("core/" + platform.name() +"/url")), config.getString("core/" + platform.name() +"/file"), config.getLong("core/" + platform.name() +"/size"), config.getString("core/relnotes"), relUrl, config.getString("core/" + platform.name() +"/hash")); } } catch (Exception e) { logger.error(e.getMessage(), e); } try { // And then load the addons String[] addOnIds = config.getStringArray("addon"); for (String id: addOnIds) { logger.debug("Found addon " + id); AddOn ao; try { ao = new AddOn(id, downloadDir, config.configurationAt("addon_" + id)); ao.setInstallationStatus(AddOn.InstallationStatus.AVAILABLE); } catch (Exception e) { logger.warn("Failed to create add-on for " + id, e); continue; } if (ao.canLoadInCurrentVersion()) { // Ignore ones that dont apply to this version this.addOns.add(ao); } else { logger.debug("Ignoring addon " + ao.getName() + " cant load in this version"); } } } catch (Exception e) { logger.error(e.getMessage(), e); } } public AddOnCollection (File[] dirs) { if (dirs != null) { for (File dir : dirs) { try { this.addDirectory(dir); } catch (Exception e) { logger.error(e.getMessage(), e); } } } } private void addDirectory (File dir) throws Exception { if (dir == null) { logger.error("Null directory supplied"); return; } if (! dir.exists()) { logger.error("No such directory: " + dir.getAbsolutePath()); } if (! dir.isDirectory()) { logger.error("Not a directory: " + dir.getAbsolutePath()); } // Load the addons try (DirectoryStream<Path> addOnFiles = Files.newDirectoryStream(dir.toPath(), "*" + AddOn.FILE_EXTENSION)) { for (Path addOnFile : addOnFiles) { if (AddOn.isAddOn(addOnFile)) { AddOn ao = createAddOn(addOnFile); if (ao == null) { continue; } boolean add = true; for (AddOn addOn : addOns) { if (ao.isSameAddOn(addOn)) { if (ao.isUpdateTo(addOn)) { if (ao.canLoadInCurrentVersion()) { // Replace in situ so we're not changing a list we're iterating through logger.debug("Addon " + addOn.getId() + " version " + addOn.getFileVersion() + " superceeded by " + ao.getFileVersion()); addOns.remove(addOn); } else { if (logger.isDebugEnabled()) { logger.debug("Ignoring newer addon " + ao.getId() + " version " + ao.getFileVersion() + " because of ZAP version constraints; Not before=" + ao.getNotBeforeVersion() + " Not from=" + ao.getNotFromVersion() + " Current Version=" + Constant.PROGRAM_VERSION); } add = false; } } else { // Same or older version, dont include logger.debug("Addon " + ao.getId() + " version " + ao.getFileVersion() + " not latest."); add = false; } break; } } if (add) { logger.debug("Found addon " + ao.getId() + " version " + ao.getFileVersion()); this.addOns.add(ao); } } } } } private static AddOn createAddOn(Path addOnFile) { try { return new AddOn(addOnFile); } catch (Exception e) { logger.warn("Failed to create add-on for: " + addOnFile.toString(), e); } return null; } /** * Gets all add-ons of this add-on collection. * * @return a {@code List} with all add-ons of the collection * @see #getInstalledAddOns() */ public List <AddOn> getAddOns() { return this.addOns; } /** * Gets all installed add-ons of this add-on collection, that is, the add-ons whose installation status is {@code INSTALLED}. * * @return a {@code List} with all installed add-ons of the collection * @see #getAddOns() * @see AddOn.InstallationStatus#INSTALLED */ public List<AddOn> getInstalledAddOns() { List<AddOn> installedAddOns = new ArrayList<>(addOns.size()); for (AddOn addOn : addOns) { if (AddOn.InstallationStatus.INSTALLED == addOn.getInstallationStatus()) { installedAddOns.add(addOn); } } return installedAddOns; } public AddOn getAddOn(String id) { for (AddOn addOn : addOns) { if (addOn.getId().equals(id)) { return addOn; } } return null; } /** * Returns a list of addons from the supplied collection that are newer than the equivalent ones in this collection * @param aoc the collection to compare with * @return a list of addons from the supplied collection that are newer than the equivalent ones in this collection */ public List <AddOn> getUpdatedAddOns(AddOnCollection aoc) { List<AddOn> updatedAddOns = new ArrayList<>(); for (AddOn ao : aoc.getAddOns()) { for (AddOn addOn : addOns) { try { if (ao.isSameAddOn(addOn) && ao.isUpdateTo(addOn)) { // Its an update to one in this collection updatedAddOns.add(ao); } } catch (Exception e) { logger.error(e.getMessage(), e); } } } return updatedAddOns; } /** * Returns a list of addons from the supplied collection that are newer than the equivalent ones in this collection * @param aoc the collection to compare with * @return a list of addons from the supplied collection that are newer than the equivalent ones in this collection */ public List <AddOn> getNewAddOns(AddOnCollection aoc) { List<AddOn> newAddOns = new ArrayList<>(); for (AddOn ao : aoc.getAddOns()) { boolean isNew = true; for (AddOn addOn : addOns) { try { if (ao.isSameAddOn(addOn)) { isNew = false; break; } } catch (Exception e) { logger.error(e.getMessage(), e); } } if (isNew) { newAddOns.add(ao); } } return newAddOns; } public ZapRelease getZapRelease() { return zapRelease; } public boolean includesAddOn(String id) { boolean inc = false; for (AddOn addOn : addOns) { if (addOn.getId().equals(id)) { return true; } } return inc; } public boolean addAddOn(AddOn ao) { if (this.includesAddOn(ao.getId())) { return false; } this.addOns.add(ao); return true; } public boolean removeAddOn(AddOn ao) { for (AddOn addOn : addOns) { if (addOn.getId().equals(ao.getId())) { addOns.remove(addOn); return true; } } return false; } }