/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2011 The Zed Attack Proxy Project
*
* 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.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.net.ssl.SSLHandshakeException;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileFilter;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLPropertiesConfiguration;
import org.apache.commons.httpclient.URI;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.parosproxy.paros.CommandLine;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.extension.CommandLineArgument;
import org.parosproxy.paros.extension.CommandLineListener;
import org.parosproxy.paros.extension.Extension;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.model.FileCopier;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.network.HttpStatusCode;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.ZAP;
import org.zaproxy.zap.control.AddOn;
import org.zaproxy.zap.control.AddOn.AddOnRunRequirements;
import org.zaproxy.zap.control.AddOnCollection;
import org.zaproxy.zap.control.AddOnCollection.Platform;
import org.zaproxy.zap.control.AddOnRunIssuesUtils;
import org.zaproxy.zap.control.AddOnUninstallationProgressCallback;
import org.zaproxy.zap.control.ExtensionFactory;
import org.zaproxy.zap.control.ZapRelease;
import org.zaproxy.zap.extension.autoupdate.AddOnDependencyChecker.AddOnChangesResult;
import org.zaproxy.zap.extension.autoupdate.AddOnDependencyChecker.UninstallationResult;
import org.zaproxy.zap.extension.autoupdate.UninstallationProgressDialogue.AddOnUninstallListener;
import org.zaproxy.zap.extension.autoupdate.UninstallationProgressDialogue.UninstallationProgressEvent;
import org.zaproxy.zap.extension.autoupdate.UninstallationProgressDialogue.UninstallationProgressHandler;
import org.zaproxy.zap.extension.log4j.ExtensionLog4j;
import org.zaproxy.zap.utils.ZapXmlConfiguration;
import org.zaproxy.zap.view.ScanStatus;
import org.zaproxy.zap.view.ZapMenuItem;
public class ExtensionAutoUpdate extends ExtensionAdaptor implements CheckForUpdateCallback, CommandLineListener {
// The short URL means that the number of checkForUpdates can be tracked - see https://bitly.com/u/psiinon
// Note that URLs must now use https (unless you change the code;)
private static final String ZAP_VERSIONS_REL_XML_SHORT = "https://bit.ly/owaspzap-2-6-0";
private static final String ZAP_VERSIONS_REL_XML_FULL = "https://raw.githubusercontent.com/zaproxy/zap-admin/master/ZapVersions-2.6.xml";
private static final String ZAP_VERSIONS_DEV_XML_SHORT = "https://bit.ly/owaspzap-dev";
private static final String ZAP_VERSIONS_DEV_XML_FULL = "https://raw.githubusercontent.com/zaproxy/zap-admin/master/ZapVersions-dev.xml";
private static final String ZAP_VERSIONS_WEEKLY_XML_SHORT = "https://bit.ly/owaspzap-devw";
// URLs for use when testing locally ;)
//private static final String ZAP_VERSIONS_XML_SHORT = "https://localhost:8080/zapcfu/ZapVersions.xml";
//private static final String ZAP_VERSIONS_XML_FULL = "https://localhost:8080/zapcfu/ZapVersions.xml";
private static final String VERSION_FILE_NAME = "ZapVersions.xml";
private ZapMenuItem menuItemCheckUpdate = null;
private ZapMenuItem menuItemLoadAddOn = null;
private static final Logger logger = Logger.getLogger(ExtensionAutoUpdate.class);
private HttpSender httpSender = null;
private DownloadManager downloadManager = null;
private ManageAddOnsDialog addonsDialog = null;
//private UpdateDialog updateDialog = null;
private Thread downloadProgressThread = null;
private Thread remoteCallThread = null;
private ScanStatus scanStatus = null;
private JButton addonsButton = null;
private JButton outOfDateButton = null;
private AddOnCollection latestVersionInfo = null;
private AddOnCollection localVersionInfo = null;
private AddOnCollection previousVersionInfo = null;
private AutoUpdateAPI api = null;
private boolean oldZapAlertAdded = false;
private boolean noCfuAlertAdded = false;
// Files currently being downloaded
private List<Downloader> downloadFiles = new ArrayList<>();
private static final int ARG_CFU_INSTALL_IDX = 0;
private static final int ARG_CFU_INSTALL_ALL_IDX = 1;
private static final int ARG_CFU_UNINSTALL_IDX = 2;
private static final int ARG_CFU_UPDATE_IDX = 3;
private static final int ARG_CFU_LIST_IDX = 4;
private static final int[] ARG_IDXS = {
ARG_CFU_INSTALL_IDX,
ARG_CFU_INSTALL_ALL_IDX,
ARG_CFU_UNINSTALL_IDX,
ARG_CFU_UPDATE_IDX,
ARG_CFU_LIST_IDX};
private CommandLineArgument[] arguments = new CommandLineArgument[ARG_IDXS.length];
public ExtensionAutoUpdate() {
super();
initialize();
}
/**
* This method initializes this
*/
private void initialize() {
this.setName("ExtensionAutoUpdate");
this.setOrder(1); // High order so that cmdline updates are installed asap
this.downloadManager = new DownloadManager(Model.getSingleton().getOptionsParam().getConnectionParam());
this.downloadManager.start();
// Do this before it can get overwritten by the latest one
this.getPreviousVersionInfo();
}
@Override
public void postInit() {
switch (ZAP.getProcessType()) {
case cmdline:
case daemon:
case zaas:
this.warnIfOutOfDate();
break;
case desktop:
default:
break;
}
}
/**
* This method initializes menuItemEncoder
*
* @return javax.swing.JMenuItem
*/
private ZapMenuItem getMenuItemCheckUpdate() {
if (menuItemCheckUpdate == null) {
menuItemCheckUpdate = new ZapMenuItem("cfu.help.menu.check",
KeyStroke.getKeyStroke(KeyEvent.VK_U, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
menuItemCheckUpdate.setText(Constant.messages.getString("cfu.help.menu.check"));
menuItemCheckUpdate.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
getAddOnsDialog().setVisible(true);
getAddOnsDialog().checkForUpdates();
}
});
}
return menuItemCheckUpdate;
}
private ZapMenuItem getMenuItemLoadAddOn() {
if (menuItemLoadAddOn == null) {
menuItemLoadAddOn = new ZapMenuItem("cfu.file.menu.loadaddon",
KeyStroke.getKeyStroke(KeyEvent.VK_L, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), false));
menuItemLoadAddOn.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
try {
JFileChooser chooser = new JFileChooser(Model.getSingleton().getOptionsParam().getUserDirectory());
File file = null;
chooser.setFileFilter(new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
} else if (file.isFile() && AddOn.isAddOnFileName(file.getName())) {
return true;
}
return false;
}
@Override
public String getDescription() {
return Constant.messages.getString("file.format.zap.addon");
}
});
int rc = chooser.showOpenDialog(View.getSingleton().getMainFrame());
if(rc == JFileChooser.APPROVE_OPTION) {
file = chooser.getSelectedFile();
if (file == null) {
return;
}
installLocalAddOn(file.toPath());
}
} catch (Exception e1) {
logger.error(e1.getMessage(), e1);
}
}
});
}
return menuItemLoadAddOn;
}
private void installLocalAddOn(Path file) throws Exception {
if (!AddOn.isAddOn(file)) {
showWarningMessageInvalidAddOnFile();
return;
}
AddOn ao;
try {
ao = new AddOn(file);
} catch (Exception e) {
showWarningMessageInvalidAddOnFile();
return;
}
if (!ao.canLoadInCurrentVersion()) {
showWarningMessageCantLoadAddOn(ao);
return;
}
AddOnDependencyChecker dependencyChecker = new AddOnDependencyChecker(getLocalVersionInfo(), latestVersionInfo == null
? getLocalVersionInfo()
: latestVersionInfo);
boolean update = false;
AddOnChangesResult result;
AddOn installedAddOn = getLocalVersionInfo().getAddOn(ao.getId());
if (installedAddOn != null) {
if (!ao.isUpdateTo(installedAddOn)) {
View.getSingleton().showWarningDialog(
MessageFormat.format(
Constant.messages.getString("cfu.warn.addOnOlderVersion"),
installedAddOn.getFileVersion(),
View.getSingleton().getStatusUI(installedAddOn.getStatus()).toString(),
ao.getFileVersion(),
View.getSingleton().getStatusUI(ao.getStatus()).toString()));
return;
}
result = dependencyChecker.calculateUpdateChanges(ao);
update = true;
} else {
result = dependencyChecker.calculateInstallChanges(ao);
}
if (result.getOldVersions().isEmpty() && result.getUninstalls().isEmpty()) {
AddOnRunRequirements reqs = ao.calculateRunRequirements(getLocalVersionInfo().getAddOns());
if (!reqs.isRunnable()) {
if (!AddOnRunIssuesUtils.askConfirmationAddOnNotRunnable(
Constant.messages.getString("cfu.warn.addOnNotRunnable.message"),
Constant.messages.getString("cfu.warn.addOnNotRunnable.question"),
getLocalVersionInfo(),
ao)) {
return;
}
}
installLocalAddOn(ao);
return;
}
if (update) {
if (!dependencyChecker.confirmUpdateChanges(getView().getMainFrame(), result)) {
return;
}
// The new version of the add-on is installed manually
result.getNewVersions().remove(ao);
} else {
if (!dependencyChecker.confirmInstallChanges(getView().getMainFrame(), result)) {
return;
}
// The add-on is installed manually
result.getInstalls().remove(ao);
}
processAddOnChanges(getView().getMainFrame(), result);
installLocalAddOn(ao);
}
private void installLocalAddOn(AddOn ao) {
File addOnFile;
try {
addOnFile = copyAddOnFileToLocalPluginFolder(ao);
} catch (FileAlreadyExistsException e) {
showWarningMessageAddOnFileAlreadyExists(e.getFile(), e.getOtherFile());
logger.warn("Unable to copy add-on, a file with the same name already exists.", e);
return;
} catch (IOException e) {
showWarningMessageUnableToCopyAddOnFile();
logger.warn("Unable to copy add-on to local plugin folder.", e);
return;
}
ao.setFile(addOnFile);
install(ao);
}
private void showWarningMessageInvalidAddOnFile() {
View.getSingleton().showWarningDialog(Constant.messages.getString("cfu.warn.invalidAddOn"));
}
private void showWarningMessageCantLoadAddOn(AddOn ao) {
String message = MessageFormat.format(
Constant.messages.getString("cfu.warn.cantload"),
ao.getNotBeforeVersion(),
ao.getNotFromVersion());
View.getSingleton().showWarningDialog(message);
}
private static File copyAddOnFileToLocalPluginFolder(AddOn addOn) throws IOException {
if (isFileInLocalPluginFolder(addOn.getFile())) {
return addOn.getFile();
}
File targetFile = new File(Constant.FOLDER_LOCAL_PLUGIN, addOn.getNormalisedFileName());
if (targetFile.exists()) {
throw new FileAlreadyExistsException(addOn.getFile().getAbsolutePath(), targetFile.getAbsolutePath(), "");
}
FileCopier fileCopier = new FileCopier();
fileCopier.copy(addOn.getFile(), targetFile);
return targetFile;
}
private static boolean isFileInLocalPluginFolder(File file) {
File fileLocalPluginFolder = new File(Constant.FOLDER_LOCAL_PLUGIN, file.getName());
if (fileLocalPluginFolder.getAbsolutePath().equals(file.getAbsolutePath())) {
return true;
}
return false;
}
private static void showWarningMessageAddOnFileAlreadyExists(String file, String targetFile) {
String message = MessageFormat.format(Constant.messages.getString("cfu.warn.addOnAlreadExists"), file, targetFile);
View.getSingleton().showWarningDialog(message);
}
private static void showWarningMessageUnableToCopyAddOnFile() {
String pathPluginFolder = new File(Constant.FOLDER_LOCAL_PLUGIN).getAbsolutePath();
String message = MessageFormat.format(Constant.messages.getString("cfu.warn.unableToCopyAddOn"), pathPluginFolder);
View.getSingleton().showWarningDialog(message);
}
private synchronized ManageAddOnsDialog getAddOnsDialog() {
if (addonsDialog == null) {
addonsDialog = new ManageAddOnsDialog(this, this.getCurrentVersion(), getLocalVersionInfo());
if (this.previousVersionInfo != null) {
addonsDialog.setPreviousVersionInfo(this.previousVersionInfo);
}
if (this.latestVersionInfo != null) {
addonsDialog.setLatestVersionInfo(this.latestVersionInfo);
}
}
return addonsDialog;
}
private void downloadFile (URL url, File targetFile, long size, String hash) {
if (View.isInitialised()) {
// Report info to the Output tab
View.getSingleton().getOutputPanel().append(
MessageFormat.format(
Constant.messages.getString("cfu.output.downloading") + "\n",
url.toString(),
targetFile.getAbsolutePath()));
}
this.downloadFiles.add(this.downloadManager.downloadFile(url, targetFile, size, hash));
if (View.isInitialised()) {
// Means we do have a UI
if (this.downloadProgressThread != null && ! this.downloadProgressThread.isAlive()) {
this.downloadProgressThread = null;
}
if (this.downloadProgressThread == null) {
this.downloadProgressThread = new Thread() {
@Override
public void run() {
while (downloadManager.getCurrentDownloadCount() > 0) {
getScanStatus().setScanCount(downloadManager.getCurrentDownloadCount());
if (addonsDialog != null && addonsDialog.isVisible()) {
addonsDialog.showProgress();
}
try {
sleep(100);
} catch (InterruptedException e) {
// Ignore
}
}
// Complete download progress
if (addonsDialog != null) {
addonsDialog.showProgress();
}
getScanStatus().setScanCount(0);
installNewExtensions();
}
};
this.downloadProgressThread.start();
}
}
}
public void installNewExtensions() {
final OptionsParamCheckForUpdates options = getModel().getOptionsParam().getCheckForUpdatesParam();
List<Downloader> handledFiles = new ArrayList<>();
for (Downloader dl : downloadFiles) {
if (dl.getFinished() == null) {
continue;
}
handledFiles.add(dl);
try {
if (!dl.isValidated()) {
logger.debug("Ignoring unvalidated download: " + dl.getUrl());
if (addonsDialog != null) {
addonsDialog.notifyAddOnDownloadFailed(dl.getUrl().toString());
} else {
String url = dl.getUrl().toString();
for (AddOn addOn : latestVersionInfo.getAddOns()) {
if (url.equals(addOn.getUrl().toString())) {
addOn.setInstallationStatus(AddOn.InstallationStatus.AVAILABLE);
break;
}
}
}
} else if (AddOn.isAddOn(dl.getTargetFile().toPath())) {
File f = dl.getTargetFile();
if (! options.getDownloadDirectory().equals(dl.getTargetFile().getParentFile())) {
// Move the file to the specified directory - we do this after its been downloaded
// as these directories can be shared, and other ZAP instances could get incomplete
// add-ons
try {
f = new File(options.getDownloadDirectory(), dl.getTargetFile().getName());
logger.info("Moving downloaded add-on from " + dl.getTargetFile().getAbsolutePath() +
" to " + f.getAbsolutePath());
FileUtils.moveFile(dl.getTargetFile(), f);
} catch (Exception e) {
if (!f.exists() && dl.getTargetFile().exists()) {
logger.error("Failed to move downloaded add-on from " + dl.getTargetFile().getAbsolutePath() +
" to " + f.getAbsolutePath() + " - left at original location", e);
f = dl.getTargetFile();
} else {
logger.error("Failed to move downloaded add-on from " + dl.getTargetFile().getAbsolutePath() +
" to " + f.getAbsolutePath() + " - skipping", e);
continue;
}
}
}
AddOn ao = new AddOn(f.toPath());
if (ao.canLoadInCurrentVersion()) {
install(ao);
} else {
logger.info("Cant load add-on " + ao.getName() +
" Not before=" + ao.getNotBeforeVersion() + " Not from=" + ao.getNotFromVersion() +
" Version=" + Constant.PROGRAM_VERSION);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
for (Downloader dl : handledFiles) {
// Cant remove in loop above as we're iterating through the list
this.downloadFiles.remove(dl);
}
}
public int getDownloadProgressPercent(URL url) throws Exception {
return this.downloadManager.getProgressPercent(url);
}
public int getCurrentDownloadCount() {
return this.downloadManager.getCurrentDownloadCount();
}
@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
if (getView() != null) {
extensionHook.getHookMenu().addHelpMenuItem(getMenuItemCheckUpdate());
extensionHook.getHookMenu().addFileMenuItem(getMenuItemLoadAddOn());
View.getSingleton().addMainToolbarButton(getAddonsButton());
View.getSingleton().getMainFrame().getMainFooterPanel().addFooterToolbarRightLabel(getScanStatus().getCountLabel());
}
extensionHook.addCommandLine(getCommandLineArguments());
this.api = new AutoUpdateAPI(this);
this.api.addApiOptions(getModel().getOptionsParam().getCheckForUpdatesParam());
extensionHook.addApiImplementor(this.api);
}
private ScanStatus getScanStatus() {
if (scanStatus == null) {
scanStatus = new ScanStatus(
new ImageIcon(
ExtensionLog4j.class.getResource("/resource/icon/fugue/download.png")),
Constant.messages.getString("cfu.downloads.icon.title"));
}
return scanStatus;
}
private JButton getAddonsButton() {
if (addonsButton == null) {
addonsButton = new JButton();
addonsButton.setIcon(new ImageIcon(ExtensionAutoUpdate.class.getResource("/resource/icon/fugue/block.png")));
addonsButton.setToolTipText(Constant.messages.getString("cfu.button.addons.browse"));
addonsButton.setEnabled(true);
addonsButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
getAddOnsDialog().setVisible(true);
}
});
}
return this.addonsButton;
}
@Override
public String getAuthor() {
return Constant.ZAP_TEAM;
}
@Override
public String getDescription() {
return Constant.messages.getString("autoupdate.desc");
}
@Override
public URL getURL() {
try {
return new URL(Constant.ZAP_HOMEPAGE);
} catch (MalformedURLException e) {
return null;
}
}
@Override
public void destroy() {
this.downloadManager.shutdown(true);
}
private HttpSender getHttpSender() {
if (httpSender == null) {
httpSender = new HttpSender(Model.getSingleton().getOptionsParam().getConnectionParam(), true,
HttpSender.CHECK_FOR_UPDATES_INITIATOR);
}
return httpSender;
}
private int dayDiff(Date d1, Date d2) {
long diff = d1.getTime() - d2.getTime();
return (int) (diff / (1000 * 60 * 60 * 24));
}
public void alertIfNewVersions() {
// Kicks off a thread and pops up a window if there are new versions.
// Depending on the options the user has chosen.
// Only expect this to be called on startup and in desktop mode
final OptionsParamCheckForUpdates options = getModel().getOptionsParam().getCheckForUpdatesParam();
if (View.isInitialised()) {
if (options.isCheckOnStartUnset()) {
// First time in
int result = getView().showConfirmDialog(
Constant.messages.getString("cfu.confirm.startCheck"));
if (result == JOptionPane.OK_OPTION) {
options.setCheckOnStart(true);
options.setCheckAddonUpdates(true);
options.setDownloadNewRelease(true);
} else {
options.setCheckOnStart(false);
}
// Save
try {
this.getModel().getOptionsParam().getConfig().save();
} catch (ConfigurationException ce) {
logger.error(ce.getMessage(), ce);
getView().showWarningDialog(
Constant.messages.getString("cfu.confirm.error"));
return;
}
}
if (! options.isCheckOnStart()) {
alertIfOutOfDate(false);
return;
}
}
if (! options.checkOnStart()) {
// Top level option not set, dont do anything, unless already downloaded last release
if (View.isInitialised() && this.getPreviousVersionInfo() != null) {
ZapRelease rel = this.getPreviousVersionInfo().getZapRelease();
if (rel != null && rel.isNewerThan(this.getCurrentVersion())) {
File f = new File(Constant.FOLDER_LOCAL_PLUGIN, rel.getFileName());
if (f.exists() && f.length() >= rel.getSize()) {
// Already downloaded, prompt to install and exit
this.promptToLaunchReleaseAndClose(rel.getVersion(), f);
}
}
}
return;
}
// Handle the response in a callback
this.getLatestVersionInfo(this);
}
private void warnIfOutOfDate() {
final OptionsParamCheckForUpdates options = getModel().getOptionsParam().getCheckForUpdatesParam();
Date today = new Date();
Date releaseCreated = Constant.getReleaseCreateDate();
if (releaseCreated != null) {
// Should only be null for dev builds
if (dayDiff(today, releaseCreated) > 365) {
// Oh no, its more than a year old!
if (ZAP.getProcessType().equals(ZAP.ProcessType.cmdline)) {
CommandLine.error("This ZAP installation is over a year old - its probably very out of date");
} else {
logger.warn("This ZAP installation is over a year old - its probably very out of date");
}
return;
}
}
Date lastChecked = options.getDayLastChecked();
Date installDate = Constant.getInstallDate();
if (installDate == null || dayDiff(today, installDate) < 90) {
// Dont warn if installed in the last 3 months
} else if (lastChecked == null || dayDiff(today, lastChecked) > 90) {
// Not checked for update in 3 months :(
if (ZAP.getProcessType().equals(ZAP.ProcessType.cmdline)) {
CommandLine.error("No check for updates for over 3 month - add-ons may well be out of date");
} else {
logger.warn("No check for updates for over 3 month - add-ons may well be out of date");
}
}
}
private void alertIfOutOfDate(boolean alwaysPrompt) {
final OptionsParamCheckForUpdates options = getModel().getOptionsParam().getCheckForUpdatesParam();
Date today = new Date();
Date releaseCreated = Constant.getReleaseCreateDate();
Date lastInstallWarning = options.getDayLastInstallWarned();
int result = -1;
logger.debug("Install created " + releaseCreated);
if (releaseCreated != null) {
// Should only be null for dev builds
int daysOld = dayDiff(today, releaseCreated);
logger.debug("Install is " + daysOld + " days old");
if (daysOld > 365) {
// Oh no, its more than a year old!
boolean setCfuOnStart = false;
if (alwaysPrompt || lastInstallWarning == null || dayDiff(today, lastInstallWarning) > 30) {
JCheckBox cfuOnStart = new JCheckBox(Constant.messages.getString("cfu.label.cfuonstart"));
cfuOnStart.setSelected(true);
String msg = Constant.messages.getString("cfu.label.oldzap");
result = View.getSingleton().showYesNoDialog(
View.getSingleton().getMainFrame(), new Object[]{msg, cfuOnStart} );
setCfuOnStart = cfuOnStart.isSelected();
}
options.setDayLastInstallWarned();
if (result == JOptionPane.OK_OPTION) {
if (setCfuOnStart) {
options.setCheckOnStart(true);
}
getAddOnsDialog().setVisible(true);
getAddOnsDialog().checkForUpdates();
} else if (!oldZapAlertAdded){
JButton button = new JButton(Constant.messages.getString("cfu.label.outofdatezap"));
button.setIcon(new ImageIcon(
ExtensionAutoUpdate.class.getResource("/resource/icon/16/050.png"))); // Alert triangle
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
alertIfOutOfDate(true);
}
});
View.getSingleton().getMainFrame().getMainFooterPanel().addFooterToolbarLeftComponent(button );
oldZapAlertAdded = true;
}
return;
}
}
Date lastChecked = options.getDayLastChecked();
Date lastUpdateWarning = options.getDayLastUpdateWarned();
Date installDate = Constant.getInstallDate();
if (installDate == null || dayDiff(today, installDate) < 90) {
// Dont warn if installed in the last 3 months
} else if (lastChecked == null || dayDiff(today, lastChecked) > 90) {
// Not checked for updates in 3 months :(
boolean setCfuOnStart = false;
if (alwaysPrompt || lastUpdateWarning == null || dayDiff(today, lastUpdateWarning) > 30) {
JCheckBox cfuOnStart = new JCheckBox(Constant.messages.getString("cfu.label.cfuonstart"));
cfuOnStart.setSelected(true);
String msg = Constant.messages.getString("cfu.label.norecentcfu");
result = View.getSingleton().showYesNoDialog(
View.getSingleton().getMainFrame(), new Object[]{msg, cfuOnStart});
setCfuOnStart = cfuOnStart.isSelected();
}
options.setDayLastUpdateWarned();
if (result == JOptionPane.OK_OPTION) {
if (setCfuOnStart) {
options.setCheckOnStart(true);
}
getAddOnsDialog().setVisible(true);
getAddOnsDialog().checkForUpdates();
if (noCfuAlertAdded) {
View.getSingleton().getMainFrame().getMainFooterPanel().
removeFooterToolbarLeftComponent(getOutOfDateButton());
}
} else if (!noCfuAlertAdded){
View.getSingleton().getMainFrame().getMainFooterPanel().
addFooterToolbarLeftComponent(getOutOfDateButton());
noCfuAlertAdded = true;
}
}
}
private JButton getOutOfDateButton() {
if (outOfDateButton == null) {
outOfDateButton = new JButton(Constant.messages.getString("cfu.label.outofdateaddons"));
outOfDateButton.setIcon(new ImageIcon(
ExtensionAutoUpdate.class.getResource("/resource/icon/16/050.png"))); // Alert triangle
outOfDateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
alertIfOutOfDate(true);
}
});
}
return outOfDateButton;
}
protected AddOnCollection getLocalVersionInfo () {
if (localVersionInfo == null) {
localVersionInfo = ExtensionFactory.getAddOnLoader().getAddOnCollection();
}
return localVersionInfo;
}
private ZapXmlConfiguration getRemoteConfigurationUrl(String url) throws
IOException, ConfigurationException, InvalidCfuUrlException {
HttpMessage msg = new HttpMessage(new URI(url, true),
Model.getSingleton().getOptionsParam().getConnectionParam());
getHttpSender().sendAndReceive(msg,true);
if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
throw new IOException();
}
if (! msg.getRequestHeader().isSecure()) {
// Only access the cfu page over https
throw new InvalidCfuUrlException(msg.getRequestHeader().getURI().toString());
}
ZapXmlConfiguration config = new ZapXmlConfiguration();
config.setDelimiterParsingDisabled(true);
config.load(new StringReader(msg.getResponseBody().toString()));
// Save version file so we can report new addons next time
File f = new File(Constant.FOLDER_LOCAL_PLUGIN, VERSION_FILE_NAME);
FileWriter out = null;
try {
out = new FileWriter(f);
out.write(msg.getResponseBody().toString());
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
// Ignore
}
}
return config;
}
protected String getLatestVersionNumber() {
if (this.getLatestVersionInfo() == null ||
this.getLatestVersionInfo().getZapRelease() == null) {
return null;
}
return this.getLatestVersionInfo().getZapRelease().getVersion();
}
protected boolean isLatestVersion() {
if (this.getLatestVersionInfo() == null ||
this.getLatestVersionInfo().getZapRelease() == null) {
return true;
}
return ! this.getLatestVersionInfo().getZapRelease().isNewerThan(this.getCurrentVersion());
}
protected boolean downloadLatestRelease() {
if (Constant.isKali()) {
if (View.isInitialised()) {
// Just tell the user to use one of the Kali options
View.getSingleton().showMessageDialog(this.getAddOnsDialog(), Constant.messages.getString("cfu.kali.options"));
}
return false;
}
if (this.getLatestVersionInfo() == null ||
this.getLatestVersionInfo().getZapRelease() == null) {
return false;
}
ZapRelease latestRelease = this.getLatestVersionInfo().getZapRelease();
if (latestRelease.isNewerThan(this.getCurrentVersion())) {
File f = new File(Constant.FOLDER_LOCAL_PLUGIN, latestRelease.getFileName());
downloadFile(latestRelease.getUrl(), f, latestRelease.getSize(), latestRelease.getHash());
return true;
}
return false;
}
private AddOnCollection getPreviousVersionInfo() {
if (this.previousVersionInfo == null) {
File f = new File(Constant.FOLDER_LOCAL_PLUGIN, VERSION_FILE_NAME);
if (f.exists()) {
try {
this.previousVersionInfo = new AddOnCollection(new ZapXmlConfiguration(f), this.getPlatform());
} catch (ConfigurationException e) {
logger.error(e.getMessage(), e);
}
}
}
return this.previousVersionInfo;
}
protected List<Downloader> getAllDownloadsProgress() {
return this.downloadManager.getProgress();
}
protected List<AddOn> getUpdatedAddOns() {
return getLocalVersionInfo().getUpdatedAddOns(this.getLatestVersionInfo());
}
protected List<AddOn> getNewAddOns() {
if (this.getPreviousVersionInfo() != null) {
return this.getPreviousVersionInfo().getNewAddOns(this.getLatestVersionInfo());
}
return getLocalVersionInfo().getNewAddOns(this.getLatestVersionInfo());
}
protected AddOn getAddOn(String id) {
AddOnCollection aoc = getLatestVersionInfo();
if (aoc != null) {
return aoc.getAddOn(id);
}
return null;
}
protected List<AddOn> getInstalledAddOns() {
return getLocalVersionInfo().getInstalledAddOns();
}
protected List<AddOn> getMarketplaceAddOns() {
AddOnCollection aoc = this.getLatestVersionInfo();
if (aoc != null) {
return aoc.getAddOns();
}
return Collections.<AddOn>emptyList();
}
protected AddOnCollection getLatestVersionInfo () {
return getLatestVersionInfo(null);
}
protected AddOnCollection getLatestVersionInfo (final CheckForUpdateCallback callback) {
if (latestVersionInfo == null) {
if (this.remoteCallThread == null || !this.remoteCallThread.isAlive()) {
this.remoteCallThread = new Thread() {
@Override
public void run() {
// Using a thread as the first call could timeout
// and we dont want the ui to hang in the meantime
this.setName("ZAP-cfu");
String shortUrl;
String longUrl;
if (Constant.isDevBuild()) {
shortUrl = ZAP_VERSIONS_DEV_XML_SHORT;
longUrl = ZAP_VERSIONS_DEV_XML_FULL;
} else if (Constant.isDailyBuild()) {
shortUrl = ZAP_VERSIONS_WEEKLY_XML_SHORT;
longUrl = ZAP_VERSIONS_DEV_XML_FULL;
} else {
shortUrl = ZAP_VERSIONS_REL_XML_SHORT;
longUrl = ZAP_VERSIONS_REL_XML_FULL;
}
logger.debug("Getting latest version info from " + shortUrl);
try {
latestVersionInfo = new AddOnCollection(getRemoteConfigurationUrl(shortUrl), getPlatform(), false);
} catch (Exception e1) {
logger.debug("Failed to access " + shortUrl, e1);
logger.debug("Getting latest version info from " + longUrl);
try {
latestVersionInfo = new AddOnCollection(getRemoteConfigurationUrl(longUrl), getPlatform(), false);
} catch (SSLHandshakeException e2) {
if (callback != null) {
callback.insecureUrl(longUrl, e2);
}
} catch (InvalidCfuUrlException e2) {
if (callback != null) {
callback.insecureUrl(longUrl, e2);
}
} catch (Exception e2) {
logger.debug("Failed to access " + longUrl, e2);
}
}
if (callback != null && latestVersionInfo != null) {
logger.debug("Calling callback with " + latestVersionInfo);
callback.gotLatestData(latestVersionInfo);
}
logger.debug("Done");
}
};
this.remoteCallThread.start();
}
if (callback == null) {
// Synchronous, but include a 30 sec max anyway
int i=0;
while (latestVersionInfo == null && this.remoteCallThread.isAlive() && i < 30) {
try {
Thread.sleep(1000);
i++;
} catch (InterruptedException e) {
// Ignore
}
}
}
}
return latestVersionInfo;
}
private String getCurrentVersion() {
// Put into local function to make it easy to manually test different scenarios;)
return Constant.PROGRAM_VERSION;
}
private Platform getPlatform() {
if (Constant.isDailyBuild()) {
return Platform.daily;
} else if (Constant.isWindows()) {
return Platform.windows;
} else if (Constant.isLinux()) {
return Platform.linux;
} else {
return Platform.mac;
}
}
protected void promptToLaunchReleaseAndClose(String version, File f) {
int ans = View.getSingleton().showConfirmDialog(
MessageFormat.format(
Constant.messages.getString("cfu.confirm.launch"),
version,
f.getAbsolutePath()));
if (ans == JOptionPane.OK_OPTION) {
Control.getSingleton().exit(false, f);
}
}
private void install(AddOn ao) {
if (! ao.canLoadInCurrentVersion()) {
throw new IllegalArgumentException("Cant load add-on " + ao.getName() +
" Not before=" + ao.getNotBeforeVersion() + " Not from=" + ao.getNotFromVersion() +
" Version=" + Constant.PROGRAM_VERSION);
}
AddOn installedAddOn = this.getLocalVersionInfo().getAddOn(ao.getId());
if (installedAddOn != null) {
if ( ! uninstallAddOn(null, installedAddOn, true)) {
// Cant uninstall the old version, so dont try to install the new one
return;
}
}
logger.info("Installing new addon " + ao.getId() + " v" + ao.getFileVersion());
if (View.isInitialised()) {
// Report info to the Output tab
View.getSingleton().getOutputPanel().append(
MessageFormat.format(
Constant.messages.getString("cfu.output.installing") + "\n",
ao.getName(),
Integer.valueOf(ao.getFileVersion())));
}
ExtensionFactory.getAddOnLoader().addAddon(ao);
if (latestVersionInfo != null) {
AddOn addOn = latestVersionInfo.getAddOn(ao.getId());
if (addOn != null && AddOn.InstallationStatus.DOWNLOADING == addOn.getInstallationStatus()) {
addOn.setInstallationStatus(AddOn.InstallationStatus.INSTALLED);
}
}
if (addonsDialog != null) {
addonsDialog.notifyAddOnInstalled(ao);
}
}
private boolean uninstall(AddOn addOn, boolean upgrading, AddOnUninstallationProgressCallback callback) {
logger.debug("Trying to uninstall addon " + addOn.getId() + " v" + addOn.getFileVersion());
boolean removedDynamically = ExtensionFactory.getAddOnLoader().removeAddOn(addOn, upgrading, callback);
if (removedDynamically) {
logger.debug("Uninstalled add-on " + addOn.getName());
if (latestVersionInfo != null) {
AddOn availableAddOn = latestVersionInfo.getAddOn(addOn.getId());
if (availableAddOn != null && availableAddOn.getInstallationStatus() != AddOn.InstallationStatus.AVAILABLE) {
availableAddOn.setInstallationStatus(AddOn.InstallationStatus.AVAILABLE);
}
}
} else {
logger.debug("Failed to uninstall add-on " + addOn.getId() + " v" + addOn.getFileVersion());
}
return removedDynamically;
}
@Override
public void insecureUrl(String url, Exception cause) {
logger.error("Failed to get check for updates on " + url, cause);
if (View.isInitialised()) {
View.getSingleton().showWarningDialog(Constant.messages.getString("cfu.warn.badurl"));
}
}
@Override
public void gotLatestData(AddOnCollection aoc) {
if (aoc == null) {
return;
}
if (getView() != null) {
// Initialise the dialogue so that it gets notifications of
// possible add-on changes and is also shown when needed
getAddOnsDialog();
}
try {
ZapRelease rel = aoc.getZapRelease();
OptionsParamCheckForUpdates options = getModel().getOptionsParam().getCheckForUpdatesParam();
if (rel.isNewerThan(getCurrentVersion())) {
logger.debug("There is a newer release: " + rel.getVersion());
// New ZAP release
if (Constant.isKali()) {
// Kali has its own package management system
if (View.isInitialised()) {
getAddOnsDialog().setVisible(true);
}
return;
}
File f = new File(Constant.FOLDER_LOCAL_PLUGIN, rel.getFileName());
if (f.exists() && f.length() >= rel.getSize()) {
// Already downloaded, prompt to install and exit
promptToLaunchReleaseAndClose(rel.getVersion(), f);
} else if (options.isDownloadNewRelease()) {
logger.debug("Auto-downloading release");
if (downloadLatestRelease() && addonsDialog != null) {
addonsDialog.setDownloadingZap();
}
} else if (addonsDialog != null) {
// Just show the dialog
addonsDialog.setVisible(true);
}
return;
}
boolean keepChecking = checkForAddOnUpdates(aoc, options);
if (keepChecking && addonsDialog != null) {
List<AddOn> newAddOns = getNewAddOns();
if (newAddOns.size() > 0) {
boolean report = false;
for (AddOn addon : newAddOns) {
switch (addon.getStatus()) {
case alpha:
if (options.isReportAlphaAddons()) {
report = true;
}
break;
case beta:
if (options.isReportBetaAddons()) {
report = true;
}
break;
case release:
if (options.isReportReleaseAddons()) {
report = true;
}
break;
default:
break;
}
}
if (report) {
getAddOnsDialog().setVisible(true);
getAddOnsDialog().selectMarketplaceTab();
}
}
}
} catch (Exception e) {
// Ignore (well, debug;), will be already logged
logger.debug(e.getMessage(), e);
}
}
private boolean checkForAddOnUpdates(AddOnCollection aoc, OptionsParamCheckForUpdates options) {
List<AddOn> updates = getUpdatedAddOns();
if (updates.isEmpty()) {
return true;
}
logger.debug("There is/are " + updates.size() + " newer addons");
AddOnDependencyChecker addOnDependencyChecker = new AddOnDependencyChecker(localVersionInfo, aoc);
Set<AddOn> addOns = new HashSet<>(updates);
AddOnDependencyChecker.AddOnChangesResult result = addOnDependencyChecker.calculateUpdateChanges(addOns);
if (!result.getUninstalls().isEmpty() || result.isNewerJavaVersionRequired()) {
if (options.isCheckAddonUpdates()) {
if (addonsDialog != null) {
// Just show the dialog
getAddOnsDialog().setVisible(true);
return false;
}
logger.info("Updates not installed some add-ons would be uninstalled or require newer java version: "
+ result.getUninstalls());
}
return true;
}
if (options.isInstallAddonUpdates()) {
logger.debug("Auto-downloading addons");
processAddOnChanges(null, result);
return false;
}
if (options.isInstallScannerRules()) {
for (Iterator<AddOn> it = addOns.iterator(); it.hasNext();) {
if (!it.next().getId().contains("scanrules")) {
it.remove();
}
}
logger.debug("Auto-downloading scanner rules");
processAddOnChanges(null, addOnDependencyChecker.calculateUpdateChanges(addOns));
return false;
}
if (options.isCheckAddonUpdates() && addonsDialog != null) {
// Just show the dialog
addonsDialog.setVisible(true);
return false;
}
return true;
}
/**
* Processes the given add-on changes.
*
* @param caller the caller to set as parent of shown dialogues
* @param changes the changes that will be processed
*/
void processAddOnChanges(Window caller, AddOnDependencyChecker.AddOnChangesResult changes) {
if (addonsDialog != null) {
addonsDialog.setDownloadingUpdates();
}
if (getView() != null) {
Set<AddOn> addOns = new HashSet<>(changes.getUninstalls());
addOns.addAll(changes.getOldVersions());
Set<Extension> extensions = new HashSet<>();
extensions.addAll(changes.getUnloadExtensions());
extensions.addAll(changes.getSoftUnloadExtensions());
if (!warnUnsavedResourcesOrActiveActions(caller, addOns, extensions, true)) {
return;
}
}
uninstallAddOns(caller, changes.getUninstalls(), false);
Set<AddOn> allAddons = new HashSet<>(changes.getNewVersions());
allAddons.addAll(changes.getInstalls());
for (AddOn addOn : allAddons) {
if (addonsDialog != null) {
addonsDialog.notifyAddOnDownloading(addOn);
}
downloadAddOn(addOn);
}
}
boolean warnUnsavedResourcesOrActiveActions(
Window caller,
Collection<AddOn> addOns,
Set<Extension> extensions,
boolean updating) {
Set<AddOn> allAddOns = new HashSet<>(addOns);
addDependents(allAddOns);
String baseMessagePrefix = updating ? "cfu.update." : "cfu.uninstall.";
String unsavedResources = getExtensionsUnsavedResources(addOns, extensions);
String activeActions = getExtensionsActiveActions(addOns, extensions);
String message = null;
if (!unsavedResources.isEmpty()) {
if (activeActions.isEmpty()) {
message = MessageFormat.format(
Constant.messages.getString(baseMessagePrefix + "message.resourcesNotSaved"),
unsavedResources);
} else {
message = MessageFormat.format(
Constant.messages.getString(baseMessagePrefix + "message.resourcesNotSavedAndActiveActions"),
unsavedResources,
activeActions);
}
} else if (!activeActions.isEmpty()) {
message = MessageFormat.format(
Constant.messages.getString(baseMessagePrefix + "message.activeActions"),
activeActions);
}
if (message != null
&& JOptionPane.showConfirmDialog(
getWindowParent(caller),
message,
Constant.PROGRAM_NAME,
JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
return false;
}
return true;
}
private void addDependents(Set<AddOn> addOns) {
for (AddOn availableAddOn : localVersionInfo.getInstalledAddOns()) {
if (availableAddOn.dependsOn(addOns) && !addOns.contains(availableAddOn)) {
addOns.add(availableAddOn);
addDependents(addOns);
}
}
}
private Window getWindowParent(Window caller) {
if (caller != null) {
return caller;
}
if (addonsDialog != null && addonsDialog.isFocused()) {
return addonsDialog;
}
return getView().getMainFrame();
}
/**
* Returns all unsaved resources of the given {@code addOns} and {@code extensions} wrapped in {@code <li>} elements or an
* empty {@code String} if there are no unsaved resources.
*
* @param addOns the add-ons that will be queried for unsaved resources
* @param extensions the extensions that will be queried for unsaved resources
* @return a {@code String} containing all unsaved resources or empty {@code String} if none
* @since 2.4.0
* @see Extension#getUnsavedResources()
*/
private static String getExtensionsUnsavedResources(Collection<AddOn> addOns, Set<Extension> extensions) {
List<String> unsavedResources = new ArrayList<>();
for (AddOn addOn : addOns) {
for (Extension extension : addOn.getLoadedExtensions()) {
List<String> resources = extension.getUnsavedResources();
if (resources != null) {
unsavedResources.addAll(resources);
}
}
}
for (Extension extension : extensions) {
List<String> resources = extension.getUnsavedResources();
if (resources != null) {
unsavedResources.addAll(resources);
}
}
return wrapEntriesInLiTags(unsavedResources);
}
private static String wrapEntriesInLiTags(List<String> entries) {
if (entries.isEmpty()) {
return "";
}
StringBuilder strBuilder = new StringBuilder(entries.size() * 15);
for (String entry : entries) {
strBuilder.append("<li>");
strBuilder.append(entry);
strBuilder.append("</li>");
}
return strBuilder.toString();
}
/**
* Returns all active actions of the given {@code addOns} and {@code extensions} wrapped in {@code <li>} elements or an
* empty {@code String} if there are no active actions.
*
* @param addOns the add-ons that will be queried for active actions
* @param extensions the extensions that will be queried for active actions
* @return a {@code String} containing all active actions or empty {@code String} if none
* @since 2.4.0
* @see Extension#getActiveActions()
*/
private static String getExtensionsActiveActions(Collection<AddOn> addOns, Set<Extension> extensions) {
List<String> activeActions = new ArrayList<>();
for (AddOn addOn : addOns) {
for (Extension extension : addOn.getLoadedExtensions()) {
List<String> actions = extension.getActiveActions();
if (actions != null) {
activeActions.addAll(actions);
}
}
}
for (Extension extension : extensions) {
List<String> resources = extension.getActiveActions();
if (resources != null) {
activeActions.addAll(resources);
}
}
return wrapEntriesInLiTags(activeActions);
}
private void downloadAddOn(AddOn addOn) {
if (AddOn.InstallationStatus.DOWNLOADING == addOn.getInstallationStatus()) {
return;
}
addOn.setInstallationStatus(AddOn.InstallationStatus.DOWNLOADING);
downloadFile(addOn.getUrl(), addOn.getFile(), addOn.getSize(), addOn.getHash());
}
private boolean uninstallAddOn(Window caller, AddOn addOn, boolean update) {
Set<AddOn> addOns = new HashSet<>();
addOns.add(addOn);
return uninstallAddOns(caller, addOns, update);
}
boolean uninstallAddOns(Window caller, Set<AddOn> addOns, boolean updates) {
if (addOns == null || addOns.isEmpty()) {
return true;
}
if (getView() != null) {
return uninstallAddOnsWithView(caller, addOns, updates, new HashSet<AddOn>());
}
final Set<AddOn> failedUninstallations = new HashSet<>();
for (AddOn addOn : addOns) {
if (!uninstall(addOn, false, null)) {
failedUninstallations.add(addOn);
}
}
if (!failedUninstallations.isEmpty()) {
logger.warn("It's recommended to restart ZAP. Not all add-ons were successfully uninstalled: "
+ failedUninstallations);
return false;
}
return true;
}
boolean uninstallAddOnsWithView(
final Window caller,
final Set<AddOn> addOns,
final boolean updates,
final Set<AddOn> failedUninstallations) {
if (addOns == null || addOns.isEmpty()) {
return true;
}
if (!EventQueue.isDispatchThread()) {
try {
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run() {
uninstallAddOnsWithView(caller, addOns, updates, failedUninstallations);
}
});
} catch (InvocationTargetException | InterruptedException e) {
logger.error("Failed to uninstall add-ons:", e);
return false;
}
return failedUninstallations.isEmpty();
}
final Window parent = getWindowParent(caller);
final UninstallationProgressDialogue waitDialogue = new UninstallationProgressDialogue(parent, addOns);
waitDialogue.addAddOnUninstallListener(new AddOnUninstallListener() {
@Override
public void uninstallingAddOn(AddOn addOn, boolean updating) {
if (updating) {
String message = MessageFormat.format(
Constant.messages.getString("cfu.output.replacing") + "\n",
addOn.getName(),
Integer.valueOf(addOn.getFileVersion()));
getView().getOutputPanel().append(message);
}
}
@Override
public void addOnUninstalled(AddOn addOn, boolean update, boolean uninstalled) {
if (uninstalled) {
if (!update && addonsDialog != null) {
addonsDialog.notifyAddOnUninstalled(addOn);
}
String message = MessageFormat.format(
Constant.messages.getString("cfu.output.uninstalled") + "\n",
addOn.getName(),
Integer.valueOf(addOn.getFileVersion()));
getView().getOutputPanel().append(message);
} else {
if (addonsDialog != null) {
addonsDialog.notifyAddOnFailedUninstallation(addOn);
}
String message;
if (update) {
message = MessageFormat.format(
Constant.messages.getString("cfu.output.replace.failed") + "\n",
addOn.getName(),
Integer.valueOf(addOn.getFileVersion()));
} else {
message = MessageFormat.format(
Constant.messages.getString("cfu.output.uninstall.failed") + "\n",
addOn.getName(),
Integer.valueOf(addOn.getFileVersion()));
}
getView().getOutputPanel().append(message);
}
}
});
SwingWorker<Void, UninstallationProgressEvent> a = new SwingWorker<Void, UninstallationProgressEvent>() {
@Override
protected void process(List<UninstallationProgressEvent> events) {
waitDialogue.update(events);
}
@Override
protected Void doInBackground() {
UninstallationProgressHandler progressHandler = new UninstallationProgressHandler() {
@Override
protected void publishEvent(UninstallationProgressEvent event) {
publish(event);
}
};
for (AddOn addOn : addOns) {
if (!uninstall(addOn, updates, progressHandler)) {
failedUninstallations.add(addOn);
}
}
if (!failedUninstallations.isEmpty()) {
logger.warn("Not all add-ons were successfully uninstalled: " + failedUninstallations);
}
return null;
}
};
waitDialogue.bind(a);
a.execute();
waitDialogue.setSynchronous(updates);
waitDialogue.setVisible(true);
return failedUninstallations.isEmpty();
}
/**
* No database tables used, so all supported
*/
@Override
public boolean supportsDb(String type) {
return true;
}
private CommandLineArgument[] getCommandLineArguments() {
arguments[ARG_CFU_INSTALL_IDX] = new CommandLineArgument("-addoninstall", 1, null, "",
"-addoninstall <addon> " + Constant.messages.getString("cfu.cmdline.install.help"));
arguments[ARG_CFU_INSTALL_ALL_IDX] = new CommandLineArgument("-addoninstallall", 0, null, "",
"-addoninstallall " + Constant.messages.getString("cfu.cmdline.installall.help"));
arguments[ARG_CFU_UNINSTALL_IDX] = new CommandLineArgument("-addonuninstall", 1, null, "",
"-addonuninstall <addon> " + Constant.messages.getString("cfu.cmdline.uninstall.help"));
arguments[ARG_CFU_UPDATE_IDX] = new CommandLineArgument("-addonupdate", 0, null, "",
"-addonupdate " + Constant.messages.getString("cfu.cmdline.update.help"));
arguments[ARG_CFU_LIST_IDX] = new CommandLineArgument("-addonlist", 0, null, "",
"-addonlist " + Constant.messages.getString("cfu.cmdline.list.help"));
return arguments;
}
/**
* Installs the specified add-ons
* @param addons The identifiers of the add-ons to be installed
* @return A string containing any error messages, will be empty if there were no problems
*/
public synchronized String installAddOns(List<String> addons) {
StringBuilder errorMessages = new StringBuilder();
AddOnCollection aoc = getLatestVersionInfo();
if (aoc == null) {
String error = Constant.messages.getString("cfu.cmdline.nocfu");
errorMessages.append(error);
CommandLine.error(error);
} else {
for (String aoName : addons) {
AddOn ao = aoc.getAddOn(aoName);
if (ao == null) {
String error = MessageFormat.format(
Constant.messages.getString("cfu.cmdline.noaddon"), aoName);
errorMessages.append(error);
errorMessages.append("\n");
CommandLine.error(error);
continue;
}
AddOnDependencyChecker addOnDependencyChecker = new AddOnDependencyChecker(getLocalVersionInfo(), aoc);
AddOnDependencyChecker.AddOnChangesResult result;
// Check to see if its already installed
AddOn iao = getLocalVersionInfo().getAddOn(aoName);
if (iao != null) {
if (!ao.isUpdateTo(iao)) {
CommandLine.info(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addoninst"),
iao.getFile().getAbsolutePath()));
continue;
}
result = addOnDependencyChecker.calculateUpdateChanges(ao);
} else {
result = addOnDependencyChecker.calculateInstallChanges(ao);
}
if (!result.getUninstalls().isEmpty()) {
String error = MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addoninst.uninstalls.required"),
result.getUninstalls());
errorMessages.append(error);
errorMessages.append("\n");
CommandLine.error(error);
continue;
}
Set<AddOn> allAddOns = new HashSet<>();
allAddOns.addAll(result.getInstalls());
allAddOns.addAll(result.getNewVersions());
for (AddOn addOn : allAddOns) {
CommandLine.info(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addonurl"),
addOn.getUrl()));
}
processAddOnChanges(null, result);
}
waitAndInstallDownloads();
}
return errorMessages.toString();
}
/**
* Uninstalls the specified add-ons
* @param addons The identifiers of the add-ons to be installed
* @return A string containing any error messages, will be empty if there were no problems
*/
public synchronized String uninstallAddOns(List<String> addons) {
StringBuilder errorMessages = new StringBuilder();
AddOnCollection aoc = this.getLocalVersionInfo();
if (aoc == null) {
String error = Constant.messages.getString("cfu.cmdline.nocfu");
errorMessages.append(error);
CommandLine.error(error);
} else {
for (String aoName : addons) {
AddOn ao = aoc.getAddOn(aoName);
if (ao == null) {
String error = MessageFormat.format(
Constant.messages.getString("cfu.cmdline.noaddon"), aoName);
errorMessages.append(error);
errorMessages.append("\n");
CommandLine.error(error);
continue;
}
AddOnDependencyChecker addOnDependencyChecker = new AddOnDependencyChecker(getLocalVersionInfo(), aoc);
Set<AddOn> addonSet = new HashSet<AddOn>();
addonSet.add(ao);
UninstallationResult result = addOnDependencyChecker.calculateUninstallChanges(addonSet);
// Check to see if other add-ons depend on it
if (result.getUninstallations().size() > 1) {
// Will always report this add-on as needing to be uninstalled
// Remove the specified add-on for the error message
result.getUninstallations().remove(ao);
String error = MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addonuninst.uninstalls.required"),
result.getUninstallations());
errorMessages.append(error);
errorMessages.append("\n");
CommandLine.error(error);
continue;
}
if (this.uninstallAddOn(null, ao, false)) {
CommandLine.info(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.uninstallok"), aoName));
} else {
String error = MessageFormat.format(
Constant.messages.getString("cfu.cmdline.uninstallfail"), aoName);
errorMessages.append(error);
errorMessages.append("\n");
CommandLine.error(error);
}
}
}
return errorMessages.toString();
}
@Override
public void execute(CommandLineArgument[] args) {
if (arguments[ARG_CFU_UPDATE_IDX].isEnabled()) {
AddOnCollection aoc = getLatestVersionInfo();
// Create some temporary options with the settings we need
OptionsParamCheckForUpdates options = new OptionsParamCheckForUpdates();
options.load(new XMLPropertiesConfiguration());
options.setCheckOnStart(true);
options.setCheckAddonUpdates(true);
options.setInstallAddonUpdates(true);
checkForAddOnUpdates(aoc, options);
waitAndInstallDownloads();
CommandLine.info(Constant.messages.getString("cfu.cmdline.updated"));
}
if (arguments[ARG_CFU_INSTALL_ALL_IDX].isEnabled()) {
AddOnCollection aoc = getLatestVersionInfo();
if (aoc == null) {
CommandLine.error(Constant.messages.getString("cfu.cmdline.nocfu"));
} else {
AddOnDependencyChecker addOnDependencyChecker = new AddOnDependencyChecker(getLocalVersionInfo(), aoc);
AddOnDependencyChecker.AddOnChangesResult result;
AddOnDependencyChecker.AddOnChangesResult allResults = null;
Set<AddOn> allAddOns = new HashSet<>();
for (AddOn ao : aoc.getAddOns()) {
if (ao.getId().equals("coreLang") && (Constant.isDevBuild() || Constant.isDailyBuild())) {
// Ignore coreLang add-on if its not a full release
// this may well be missing strings that are now necessary
continue;
}
// Check to see if its already installed
AddOn iao = getLocalVersionInfo().getAddOn(ao.getId());
if (iao != null) {
if (!ao.isUpdateTo(iao)) {
continue;
}
result = addOnDependencyChecker.calculateUpdateChanges(ao);
} else {
result = addOnDependencyChecker.calculateInstallChanges(ao);
}
if (result.getUninstalls().isEmpty()) {
allAddOns.addAll(result.getInstalls());
allAddOns.addAll(result.getNewVersions());
if (allResults == null) {
allResults = result;
} else {
allResults.addResults(result);
}
}
}
if (allAddOns.isEmpty()) {
// Nothing to do
return;
}
for (AddOn addOn : allAddOns) {
CommandLine.info(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addonurl"),
addOn.getUrl()));
}
processAddOnChanges(null, allResults);
waitAndInstallDownloads();
}
}
if (arguments[ARG_CFU_INSTALL_IDX].isEnabled()) {
Vector<String> params = arguments[ARG_CFU_INSTALL_IDX].getArguments();
installAddOns(params);
}
if (arguments[ARG_CFU_UNINSTALL_IDX].isEnabled()) {
Vector<String> params = arguments[ARG_CFU_UNINSTALL_IDX].getArguments();
uninstallAddOns(params);
}
if (arguments[ARG_CFU_LIST_IDX].isEnabled()) {
AddOnCollection aoc = this.getLocalVersionInfo();
List<AddOn> aolist = new ArrayList<AddOn>(aoc.getAddOns());
Collections.sort(aolist, new Comparator<AddOn>(){
@Override
public int compare(AddOn ao1, AddOn ao2) {
return ao1.getName().compareTo(ao2.getName());
}});
for (AddOn addon : aolist) {
CommandLine.info(addon.getName() + "\t" + addon.getId() +
"\tv" + addon.getFileVersion() + "\t" + addon.getStatus().name() +
"\t" + addon.getDescription());
}
}
}
private void waitAndInstallDownloads() {
while (downloadManager.getCurrentDownloadCount() > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// Ignore
}
}
for (Downloader download : downloadManager.getProgress()) {
if (download.isValidated()) {
CommandLine.info(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addondown"),
download.getTargetFile().getAbsolutePath()));
} else {
CommandLine.error(MessageFormat.format(
Constant.messages.getString("cfu.cmdline.addondown.failed"),
download.getTargetFile().getName()));
}
}
if (getView() == null) {
installNewExtensions();
}
}
@Override
public boolean handleFile(File file) {
// Not supported
return false;
}
@Override
public List<String> getHandledExtensions() {
// None
return null;
}
}