/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Copyright 2015 The 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.extension.autoupdate; import java.awt.EventQueue; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.event.TableModelEvent; import javax.swing.table.AbstractTableModel; import org.apache.log4j.Logger; import org.parosproxy.paros.Constant; import org.parosproxy.paros.extension.encoder.Encoder; import org.zaproxy.zap.control.AddOn; import org.zaproxy.zap.control.AddOn.AddOnRunRequirements; import org.zaproxy.zap.control.AddOnCollection; import org.zaproxy.zap.control.AddOnRunIssuesUtils; /** * An {@code AbstractTableModel} for add-ons. */ public abstract class AddOnsTableModel extends AbstractTableModel { /** * The column in the table model that allows to get the {@code AddOnWrapper} of a given row. */ public static final int COLUMN_ADD_ON_WRAPPER = -1; private static final long serialVersionUID = -5240438485136881299L; protected final Logger logger = Logger.getLogger(this.getClass()); private final Comparator<AddOnWrapper> comparator; private final List<AddOnWrapper> wrappers; private final int progressColumn; private AddOnCollection addOnCollection; private AddOnSearcher addOnSeacher; /** * @deprecated (2.5.0) Replaced by {@link #AddOnsTableModel(AddOnCollection, int)}. It will be removed in a future release. */ @Deprecated public AddOnsTableModel(Comparator<AddOnWrapper> comparator, AddOnCollection addOnCollection, int progressColumn) { super(); this.comparator = comparator; this.wrappers = new ArrayList<>(); this.progressColumn = progressColumn; this.addOnCollection = addOnCollection; } public AddOnsTableModel(AddOnCollection addOnCollection, int progressColumn) { super(); this.comparator = null; this.wrappers = new ArrayList<>(); this.progressColumn = progressColumn; this.addOnCollection = addOnCollection; } public void setAddOnCollection(AddOnCollection addOnCollection) { this.addOnCollection = addOnCollection; } protected List<AddOnWrapper> getAddOnWrappers() { return wrappers; } public Set<AddOn> getSelectedAddOns() { Set<AddOn> selectedAddOns = new HashSet<>(); for (AddOnWrapper aow : getAddOnWrappers()) { if (aow.isEnabled()) { selectedAddOns.add(aow.getAddOn()); } } return selectedAddOns; } public Set<AddOn> getDownloadingAddOns() { Set<AddOn> downloadingAddOns = new HashSet<>(); for (AddOnWrapper aow : getAddOnWrappers()) { AddOn addOn = getAddOnForDownload(aow); if (AddOn.InstallationStatus.DOWNLOADING == addOn.getInstallationStatus()) { downloadingAddOns.add(addOn); } } return downloadingAddOns; } protected AddOn getAddOnForDownload(AddOnWrapper aow) { return aow.getAddOn(); } protected void addAddOnWrapper(AddOn addOn, AddOnWrapper.Status status) { AddOnWrapper aow = createAddOnWrapper(addOn, status); int idx = 0; if (comparator != null) { for (; idx < getAddOnWrappers().size(); idx++) { if (comparator.compare(aow, getAddOnWrappers().get(idx)) < 0) { break; } getAddOnWrappers().add(idx, aow); } } else { idx = getAddOnWrappers().size(); getAddOnWrappers().add(aow); } fireTableRowsInserted(idx, idx); refreshEntries(); } protected void refreshEntries() { for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { if (refreshEntry(getAddOnWrappers().get(idx), idx)) { fireTableRowsUpdated(idx, idx); } } } protected boolean refreshEntry(AddOnWrapper aow, int row) { boolean updateRow = false; if (aow.getInstallationStatus() != aow.getAddOn().getInstallationStatus()) { aow.setInstallationStatus(aow.getAddOn().getInstallationStatus()); updateRow = true; } updateRow |= refreshRunningIssues(aow, row); return updateRow; } public void removeAddOn(AddOn addOn) { for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { if (addOn.isSameAddOn(getAddOnWrappers().get(idx).getAddOn())) { getAddOnWrappers().remove(idx); fireTableRowsDeleted(idx, idx); refreshEntries(); break; } } } public boolean notifyAddOnDownloading(AddOn addOn) { for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { AddOnWrapper aow = getAddOnWrappers().get(idx); if (addOn.isSameAddOn(getAddOnForDownload(aow))) { aow.setInstallationStatus(AddOn.InstallationStatus.DOWNLOADING); aow.setEnabled(false); fireTableRowsUpdated(idx, idx); return true; } } return false; } public boolean notifyAddOnDownloadFailed(String url) { for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { AddOnWrapper aow = getAddOnWrappers().get(idx); AddOn addOn = getAddOnForDownload(aow); if (hasSameUrl(addOn, url)) { setFailed(aow, addOn); fireTableRowsUpdated(idx, idx); return true; } } return false; } private void setFailed(AddOnWrapper aow, AddOn addOn) { aow.setFailed(true); restoreInstallationStatusFailedDownload(aow); addOn.setInstallationStatus(AddOn.InstallationStatus.AVAILABLE); } private static boolean hasSameUrl(AddOn addOn, String url) { URL addOnUrl = addOn.getUrl(); if (addOnUrl == null) { return false; } return addOnUrl.toString().equals(url); } public boolean notifyAddOnFailedUninstallation(AddOn addOn) { for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { AddOnWrapper aow = getAddOnWrappers().get(idx); if (addOn.isSameAddOn(getAddOnForDownload(aow))) { aow.setInstallationStatus(AddOn.InstallationStatus.UNINSTALLATION_FAILED); aow.setEnabled(false); fireTableRowsUpdated(idx, idx); return true; } } return false; } protected AddOnWrapper getAddOnWrapper(int rowIndex) { return this.getAddOnWrappers().get(rowIndex); } public void updateDownloadsProgresses(ExtensionAutoUpdate extension) { final List<Integer> rows = new ArrayList<>(getAddOnWrappers().size()); for (int idx = 0; idx < getAddOnWrappers().size(); idx++) { AddOnWrapper aow = getAddOnWrappers().get(idx); AddOn addOn = getAddOnForDownload(aow); if (AddOn.InstallationStatus.DOWNLOADING == addOn.getInstallationStatus()) { URL url = addOn.getUrl(); try { int progress = extension.getDownloadProgressPercent(url); if (progress > 0) { aow.setProgress(progress); rows.add(Integer.valueOf(idx)); } } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error on " + url, e); } setFailed(aow, addOn); try { final int row = idx; EventQueue.invokeAndWait(new Runnable() { @Override public void run() { fireTableRowsUpdated(row, row); } }); } catch (InvocationTargetException | InterruptedException ignore) { } } } } if (!rows.isEmpty()) { try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { for (Integer row : rows) { fireTableCellUpdated(row.intValue(), progressColumn); } } }); } catch (InvocationTargetException | InterruptedException e) { if (logger.isDebugEnabled()) { logger.debug("Failed to update all the progresses: ", e); } } } } protected abstract void restoreInstallationStatusFailedDownload(AddOnWrapper aow); protected void fireTableColumnUpdated(int firstRow, int lastRow, int column) { fireTableChanged(new TableModelEvent(this, firstRow, lastRow, column, TableModelEvent.UPDATE)); } protected AddOnWrapper createAddOnWrapper(AddOn addOn, AddOnWrapper.Status status) { if (addOnCollection == null) { return new AddOnWrapper(addOn, status); } return new AddOnWrapper( addOn, status, getAddOnRunningIssues(addOn.calculateRunRequirements(addOnCollection.getAddOns()))); } protected boolean refreshRunningIssues(AddOnWrapper aow, int row) { AddOnRunRequirements reqs = aow.getAddOn().calculateRunRequirements(addOnCollection.getAddOns()); String issues = getAddOnRunningIssues(reqs); aow.setRunningIssues(issues, !reqs.hasExtensionsWithRunningIssues()); return !issues.isEmpty(); } protected String getAddOnRunningIssues(AddOnRunRequirements reqs) { List<String> extractedIssues = AddOnRunIssuesUtils.getUiRunningIssues(reqs, getAddOnSearcher()); if (extractedIssues.isEmpty()) { List<String> extensionsIssues = AddOnRunIssuesUtils.getUiExtensionsRunningIssues(reqs, getAddOnSearcher()); if (!extensionsIssues.isEmpty()) { return getHtmlFromIssues( Constant.messages.getString("cfu.warn.addon.with.extensions.with.missing.requirements"), extensionsIssues); } return ""; } return getHtmlFromIssues(Constant.messages.getString("cfu.warn.addon.with.missing.requirements"), extractedIssues); } private static String getHtmlFromIssues(String title, List<String> issues) { StringBuilder strBuilder = new StringBuilder(150); Encoder encoder = new Encoder(); strBuilder.append("<html><strong>").append(encoder.getHTMLString(title)).append("</strong><ul>"); for (String issue : issues) { strBuilder.append("<li>").append(encoder.getHTMLString(issue)).append("</li>"); } strBuilder.append("</ul></html>"); return strBuilder.toString(); } protected AddOn getMissingAddOn(String addOnId) { return addOnCollection.getAddOn(addOnId); } private AddOnSearcher getAddOnSearcher() { if (addOnSeacher == null) { addOnSeacher = new AddOnSearcher(); } return addOnSeacher; } private class AddOnSearcher implements AddOnRunIssuesUtils.AddOnSearcher { @Override public AddOn searchAddOn(String id) { return getMissingAddOn(id); } } }