/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2010 psiinon@gmail.com
*
* 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.help;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import javax.help.CSH;
import javax.help.HelpBroker;
import javax.help.HelpSet;
import javax.help.SwingHelpUtilities;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.ViewDelegate;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.control.AddOn;
import org.zaproxy.zap.control.ExtensionFactory;
import org.zaproxy.zap.extension.AddOnInstallationStatusListener;
import org.zaproxy.zap.utils.DisplayUtils;
import org.zaproxy.zap.view.ZapMenuItem;
/**
* Loads the core help files and provides GUI elements to access them.
*/
public class ExtensionHelp extends ExtensionAdaptor {
/**
* The name of the property that has the {@code HelpSet}, assigned to a {@code JComponent}.
*/
private static final String HELP_SET_PROPERTY = "HelpSet";
/**
* The name of the property that has the ID of the help page, assigned to a {@code JComponent}.
*/
private static final String HELP_ID_PROPERTY = "HelpID";
private static final String HELP_SET_FILE_NAME = "helpset";
public static final ImageIcon HELP_ICON = DisplayUtils.getScaledIcon(
new ImageIcon(ExtensionHelp.class.getResource("/resource/icon/16/201.png")));
private ZapMenuItem menuHelpZap = null;
private JButton helpButton = null;
private static HelpSet hs = null;
private static HelpBroker hb = null;
/**
* The {@code ActionListener} to show the help dialogue (with contents matching the focused UI component, if available).
* <p>
* Lazily initialised.
*
* @see #createHelpBroker()
*/
private static ActionListener showHelpActionListener;
/**
* A {@code WeakHashMap} of {@code JComponent}s to the ID of the help page assigned to them.
* <p>
* Used to add/remove the help page from the components when the help add-on is installed/uninstalled.
*
* @see #setHelpEnabled(boolean)
*/
private static WeakHashMap<JComponent, String> componentsWithHelp;
private static final Logger logger = Logger.getLogger(ExtensionHelp.class);
public ExtensionHelp() {
super("ExtensionHelp");
this.setOrder(10000); // Set to a huge value so the help button is always on the far right of the toolbar
}
@Override
public void initView(ViewDelegate view) {
super.initView(view);
SwingHelpUtilities.setContentViewerUI(BasicOnlineContentViewerUI.class.getCanonicalName());
UIManager.getDefaults().put("ZapHelpSearchNavigatorUI", ZapBasicSearchNavigatorUI.class.getCanonicalName());
}
@Override
public void hook(ExtensionHook extensionHook) {
super.hook(extensionHook);
if (getView() != null) {
extensionHook.getHookMenu().addHelpMenuItem(getMenuHelpZapUserGuide());
View.getSingleton().addMainToolbarSeparator();
View.getSingleton().addMainToolbarButton(this.getHelpButton());
enableHelpKey(this.getView().getSiteTreePanel(), "ui.tabs.sites");
enableHelpKey(this.getView().getRequestPanel(), "ui.tabs.request");
enableHelpKey(this.getView().getResponsePanel(), "ui.tabs.response");
setHelpEnabled(getHelpBroker() != null);
extensionHook.addAddOnInstallationStatusListener(new AddOnInstallationStatusListenerImpl());
}
}
/**
* Tells whether or not the help is available.
* <p>
* The help is available when the help add-on for the currently set {@code Locale} is installed.
*
* @return {@code true} if the help is available, {@code false} otherwise
* @since 2.5.0
*/
public boolean isHelpAvailable() {
return hb != null;
}
/**
* Sets whether or not the help is enabled (menu item, buttons and help for the components).
* <p>
* The call to this method has no effect if the view is not initialised.
*
* @param enabled {@code true} if the help should be enabled, {@code false} otherwise
* @see #findHelpSetUrl()
*/
private void setHelpEnabled(boolean enabled) {
if (getView() == null) {
return;
}
JRootPane rootPane = getView().getMainFrame().getRootPane();
if (enabled && findHelpSetUrl() != null) {
createHelpBroker();
getMenuHelpZapUserGuide().addActionListener(showHelpActionListener);
getMenuHelpZapUserGuide().setToolTipText(null);
getMenuHelpZapUserGuide().setEnabled(true);
// Enable the top level F1 help key
hb.enableHelpKey(rootPane, "zap.intro", hs, "javax.help.SecondaryWindow", null);
for (Entry<JComponent, String> entry : componentsWithHelp.entrySet()) {
hb.enableHelp(entry.getKey(), entry.getValue(), hs);
}
getHelpButton().setToolTipText(Constant.messages.getString("help.button.tooltip"));
getHelpButton().setEnabled(true);
} else {
String toolTipNoHelp = Constant.messages.getString("help.error.nohelp");
getMenuHelpZapUserGuide().setEnabled(false);
getMenuHelpZapUserGuide().setToolTipText(toolTipNoHelp);
getMenuHelpZapUserGuide().removeActionListener(showHelpActionListener);
rootPane.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_HELP, 0));
rootPane.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
removeHelpProperties(rootPane);
for (JComponent component : componentsWithHelp.keySet()) {
removeHelpProperties(component);
}
getHelpButton().setEnabled(false);
getHelpButton().setToolTipText(toolTipNoHelp);
hb = null;
hs = null;
showHelpActionListener = null;
}
}
/**
* Removes the help properties from the given component.
*
* @param component the component whose help properties will be removed, must not be {@code null}
* @see #HELP_ID_PROPERTY
* @see #HELP_SET_PROPERTY
*/
private void removeHelpProperties(JComponent component) {
component.putClientProperty(HELP_ID_PROPERTY, null);
component.putClientProperty(HELP_SET_PROPERTY, null);
}
public static HelpBroker getHelpBroker() {
if (hb == null) {
createHelpBroker();
}
return hb;
}
private static synchronized void createHelpBroker() {
if (hb == null) {
try {
URL hsUrl = findHelpSetUrl();
if (hsUrl != null) {
hs = new HelpSet(ExtensionFactory.getAddOnLoader(), hsUrl);
hb = hs.createHelpBroker();
showHelpActionListener = new CSH.DisplayHelpFromFocus(hb);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
/**
* Finds and returns the {@code URL} to the {@code HelpSet} that matches the currently set {@code Locale}.
* <p>
* The name of the {@code HelpSet} searched is {@value #HELP_SET_FILE_NAME}.
*
* @return the {@code URL} to the {@code HelpSet}, {@code null} if not found
* @see Constant#getLocale()
* @see HelpSet
*/
private static URL findHelpSetUrl() {
return HelpSet.findHelpSet(ExtensionFactory.getAddOnLoader(), HELP_SET_FILE_NAME, Constant.getLocale());
}
/**
* Enables the help for the given component using the given help page ID.
* <p>
* The help page is shown when the help keyboard shortcut (F1) is pressed, while the component is focused.
*
* @param component the component that will have a help page assigned
* @param id the ID of the help page
*/
public static void enableHelpKey (Component component, String id) {
if (component instanceof JComponent) {
JComponent jComponent = (JComponent) component;
if (componentsWithHelp == null) {
componentsWithHelp = new WeakHashMap<>();
}
componentsWithHelp.put(jComponent, id);
}
if (hb != null) {
hb.enableHelp(component, id, hs);
}
}
/*
public static void enablePopupHelpKey (Component component, String key) {
if (getHelpBroker() != null) {
hb.enableHelpKey(component,
"zap.intro", hs, "javax.help.SecondaryWindow", null);
hb.enableHelp(component, key, hs);
}
}
*/
/**
* @see #showHelp(String)
*/
public static void showHelp() {
showHelp("zap.intro");
}
/**
* Shows a specific help topic
*
* @param helpindex
*/
public static void showHelp(String helpindex) {
if (getHelpBroker() == null) {
return;
}
try {
getHelpBroker().showID(helpindex, "javax.help.SecondaryWindow", null);
} catch (Exception e) {
logger.error("error loading help with index: " + helpindex, e);
}
}
private ZapMenuItem getMenuHelpZapUserGuide() {
if (menuHelpZap == null) {
menuHelpZap = new ZapMenuItem("help.menu.guide",
KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, false));
}
return menuHelpZap;
}
private JButton getHelpButton() {
if (helpButton == null) {
helpButton = new JButton();
helpButton.setIcon(new ImageIcon(ExtensionHelp.class.getResource("/resource/icon/16/201.png")));
helpButton.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(java.awt.event.ActionEvent e) {
showHelp();
}
});
}
return helpButton;
}
@Override
public String getAuthor() {
return Constant.ZAP_TEAM;
}
@Override
public String getDescription() {
return Constant.messages.getString("help.desc");
}
@Override
public URL getURL() {
try {
return new URL(Constant.ZAP_HOMEPAGE);
} catch (MalformedURLException e) {
return null;
}
}
/**
* No database tables used, so all supported
*/
@Override
public boolean supportsDb(String type) {
return true;
}
/**
* An {@code AddOnInstallationStatusListener} responsible to enabled/disable help UI components when the help add-on is
* installed/uninstalled.
*/
private class AddOnInstallationStatusListenerImpl implements AddOnInstallationStatusListener {
@Override
public void addOnInstalled(AddOn addOn) {
if (hb == null && findHelpSetUrl() != null) {
setHelpEnabled(true);
}
}
@Override
public void addOnSoftUninstalled(AddOn addOn, boolean successfully) {
addOnUninstalled(addOn, successfully);
}
@Override
public void addOnUninstalled(AddOn addOn, boolean successfully) {
if (hb != null && findHelpSetUrl() == null) {
setHelpEnabled(false);
}
}
}
}