/*******************************************************************************
* Copyright (c) 2009,2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
* Aptana Inc.
* Dawid PakuĊa [339547]
*******************************************************************************/
package org.eclipse.php.internal.debug.core.launching;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.*;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.*;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.ILaunchGroup;
import org.eclipse.equinox.security.storage.ISecurePreferences;
import org.eclipse.equinox.security.storage.SecurePreferencesFactory;
import org.eclipse.equinox.security.storage.StorageException;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.core.project.ProjectOptions;
import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersInitializer;
import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersKeys;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.preferences.CorePreferenceConstants;
import org.eclipse.php.internal.core.preferences.PreferencesSupport;
import org.eclipse.php.internal.debug.core.*;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerConfiguration;
import org.eclipse.php.internal.debug.core.preferences.PHPDebugCorePreferenceNames;
import org.eclipse.php.internal.debug.core.preferences.PHPDebuggersRegistry;
import org.eclipse.php.internal.debug.core.preferences.PHPexeItem;
import org.eclipse.php.internal.debug.core.preferences.PHPexes;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.XDebugDebuggerConfiguration;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.XDebugDebuggerSettingsUtil;
import org.eclipse.php.internal.debug.core.zend.communication.DebugConnection;
import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerConfiguration;
import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerSettingsUtil;
import org.eclipse.php.internal.debug.core.zend.model.PHPDebugTarget;
import org.eclipse.php.internal.debug.daemon.DaemonPlugin;
import org.eclipse.php.internal.server.core.Server;
import org.eclipse.php.internal.server.core.manager.ServersManager;
import org.eclipse.php.internal.server.core.tunneling.SSHTunnel;
import org.eclipse.php.internal.server.core.tunneling.SSHTunnelFactory;
import org.eclipse.php.internal.ui.PHPUiPlugin;
import org.eclipse.php.internal.ui.preferences.PreferenceConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.*;
/**
* Utilities that are shared to all the PHP launches.
*/
public class PHPLaunchUtilities {
public static final String ID_PHPDebugOutput = "org.eclipse.debug.ui.PHPDebugOutput"; //$NON-NLS-1$
public static final String ID_PHPBrowserOutput = "org.eclipse.debug.ui.PHPBrowserOutput"; //$NON-NLS-1$
private static DebuggerDelayProgressMonitorDialog progressDialog;
/**
* Display the Debug Output view in case it's hidden or not initialized. In
* case where the Browser Output view is visible, nothing will happen and
* the Browser Output will remain as the visible view during the debug
* session.
*
* Note that the behavior given by this function is mainly needed when we
* are in a PHP Perspective (not debug) and a session without a breakpoint
* was launched. So in this case a 'force' output display is triggered.
*
* This function also take into account the
* PHPDebugCorePreferenceNames.OPEN_DEBUG_VIEWS flag and does not show the
* debug views in case it was not chosen from the preferences.
*/
public static void showDebugView() {
if (!Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID,
PHPDebugCorePreferenceNames.OPEN_DEBUG_VIEWS, true, null)) {
return;
}
// Get the page through a UI thread! Otherwise, it wont work...
Display.getDefault().syncExec(new Runnable() {
public void run() {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (page != null) {
try {
IViewPart debugOutputPart = page.findView("org.eclipse.debug.ui.PHPDebugOutput"); //$NON-NLS-1$
IViewPart browserOutputPart = page.findView("org.eclipse.debug.ui.PHPBrowserOutput"); //$NON-NLS-1$
// Test if the Debug Output view is alive and visible.
boolean shouldShowDebug = false;
if (debugOutputPart == null || !page.isPartVisible(debugOutputPart)) {
shouldShowDebug = true;
}
// If the Browser Output is visible, do not switch to
// the Debug Output.
if (browserOutputPart != null && page.isPartVisible(browserOutputPart)) {
shouldShowDebug = false;
}
if (shouldShowDebug) {
page.showView("org.eclipse.debug.ui.PHPDebugOutput"); //$NON-NLS-1$
}
} catch (Exception e) {
Logger.logException("Error switching to the Debug Output view", e); //$NON-NLS-1$
}
}
}
});
}
/**
* Returns true if the is at least one active PHP debug session.
*
* @return True, if there is an active debug session; False, otherwise.
*/
public static boolean hasPHPDebugLaunch() {
ILaunch[] launches = DebugPlugin.getDefault().getLaunchManager().getLaunches();
for (int i = 0; i < launches.length; i++) {
if (!launches[i].isTerminated() && ILaunchManager.DEBUG_MODE.equals(launches[i].getLaunchMode())
&& launches[i].getDebugTarget() instanceof PHPDebugTarget) {
return true;
}
}
return false;
}
/**
* Notify the existence of a previous PHP debug session in case the user
* launched a new session.
*
* @param newLaunchConfiguration
* @param newLaunch
* @return True, if the launch can be continued; False, otherwise.
* @throws CoreException
*/
public static boolean notifyPreviousLaunches(ILaunch newLaunch) throws CoreException {
// In case the new launch is not a debug launch, we have no problem.
if (!ILaunchManager.DEBUG_MODE.equals(newLaunch.getLaunchMode())) {
return true;
}
// If there are no active debug launches, return true and continue with
// the new launch.
if (!hasPHPDebugLaunch()) {
return true;
}
// Check whether we should ask the user.
final IPreferenceStore store = PHPUiPlugin.getDefault().getPreferenceStore();
String option = store.getString(PreferenceConstants.ALLOW_MULTIPLE_LAUNCHES);
if (MessageDialogWithToggle.ALWAYS.equals(option)) {
// If always, then we should always allow the launch
return true;
}
if (MessageDialogWithToggle.NEVER.equals(option)) {
// We should never allow the launch, so display a message describing
// the situation.
final Display disp = Display.getDefault();
disp.syncExec(new Runnable() {
public void run() {
MessageDialog.openInformation(disp.getActiveShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_phpLaunchTitle,
PHPDebugCoreMessages.PHPLaunchUtilities_activeLaunchDetected);
}
});
return false;
}
final DialogResultHolder resultHolder = new DialogResultHolder();
final Display disp = Display.getDefault();
disp.syncExec(new Runnable() {
public void run() {
// Display a dialog to notify the existence of a previous active
// launch.
MessageDialogWithToggle m = MessageDialogWithToggle.openYesNoQuestion(disp.getActiveShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_confirmation,
PHPDebugCoreMessages.PHPLaunchUtilities_multipleLaunchesPrompt,
PHPDebugCoreMessages.PHPLaunchUtilities_rememberDecision, false, store,
PreferenceConstants.ALLOW_MULTIPLE_LAUNCHES);
resultHolder.setReturnCode(m.getReturnCode());
}
});
switch (resultHolder.getReturnCode()) {
case IDialogConstants.YES_ID:
case IDialogConstants.OK_ID:
return true;
case IDialogConstants.NO_ID:
return false;
}
return true;
}
/**
* Switch from the PHP debug perspective to the PHP perspective (in case we
* are not using it already). This method is called when the last active PHP
* debug session was terminated.
*/
public static void switchToPHPPerspective() {
Display display = PlatformUI.getWorkbench().getDisplay();
display.asyncExec(new Runnable() {
public void run() {
String perspectiveID = PHPUiPlugin.PERSPECTIVE_ID;
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (shouldSwitchToPHPPerspective(perspectiveID)) {
try {
workbench.showPerspective(perspectiveID, window);
} catch (WorkbenchException e) {
PHPUiPlugin.log(e);
}
}
}
});
}
// Returns true iff the PHP perspective should be displayed.
private static boolean shouldSwitchToPHPPerspective(String perspectiveID) {
// check whether we should ask the user.
IPreferenceStore store = PHPUiPlugin.getDefault().getPreferenceStore();
String option = store.getString(PreferenceConstants.SWITCH_BACK_TO_PHP_PERSPECTIVE);
if (MessageDialogWithToggle.ALWAYS.equals(option)) {
return true;
}
if (MessageDialogWithToggle.NEVER.equals(option)) {
return false;
}
// Check whether the desired perspective is already active.
IPerspectiveRegistry registry = PlatformUI.getWorkbench().getPerspectiveRegistry();
IPerspectiveDescriptor perspective = registry.findPerspectiveWithId(perspectiveID);
if (perspective == null) {
return false;
}
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window != null) {
IWorkbenchPage page = window.getActivePage();
if (page != null) {
IPerspectiveDescriptor current = page.getPerspective();
if (current != null && current.getId().equals(perspectiveID)) {
return false;
}
}
// Ask the user whether to switch
MessageDialogWithToggle m = MessageDialogWithToggle.openYesNoQuestion(window.getShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_PHPPerspectiveSwitchTitle,
NLS.bind(PHPDebugCoreMessages.PHPLaunchUtilities_PHPPerspectiveSwitchMessage,
new String[] { perspective.getLabel() }),
PHPDebugCoreMessages.PHPLaunchUtilities_rememberDecision, false, store,
PreferenceConstants.SWITCH_BACK_TO_PHP_PERSPECTIVE);
int result = m.getReturnCode();
switch (result) {
case IDialogConstants.YES_ID:
case IDialogConstants.OK_ID:
return true;
case IDialogConstants.NO_ID:
return false;
}
}
return false;
}
/**
* Make all the necessary checks to see if the current launch can be
* launched with regards to the previous launches that has 'debug all pages'
* attribute.
*
* @throws CoreException
*/
public static boolean checkDebugAllPages(final ILaunchConfiguration newLaunchConfiguration, final ILaunch newLaunch)
throws CoreException {
// If the remote debugger already supports multiple debugging with the
// 'debug all pages',
// we do not have to do a thing and we can return.
if (PHPDebugPlugin.supportsMultipleDebugAllPages()) {
return true;
}
// Make sure we set the attributes on the ILaunch since the
// ILaunchConfiguration reference never changes, while the
// ILaunch is created for each launch.
newLaunch.setAttribute(IPHPDebugConstants.DEBUGGING_PAGES, newLaunchConfiguration
.getAttribute(IPHPDebugConstants.DEBUGGING_PAGES, IPHPDebugConstants.DEBUGGING_ALL_PAGES));
checkAutoRemoveLaunches();
ILaunch[] launches = DebugPlugin.getDefault().getLaunchManager().getLaunches();
boolean hasContiniousLaunch = false;
// check for a launch that has a 'debug all pages' or 'start debug from'
// attribute
for (int i = 0; !hasContiniousLaunch && i < launches.length; i++) {
ILaunch launch = launches[i];
if (launch != newLaunch && ILaunchManager.DEBUG_MODE.equals(launch.getLaunchMode())) {
if (isDebugAllPages(launch) || isStartDebugFrom(launch)) {
hasContiniousLaunch = true;
}
}
}
// Check if the new launch is 'debug all pages'
boolean newLaunchIsDebug = ILaunchManager.DEBUG_MODE.equals(newLaunch.getLaunchMode());
final boolean newIsDebugAllPages = newLaunchIsDebug && isDebugAllPages(newLaunch);
final boolean newIsStartDebugFrom = newLaunchIsDebug && isStartDebugFrom(newLaunch);
final boolean fHasContiniousLaunch = hasContiniousLaunch;
if ((fHasContiniousLaunch || newIsDebugAllPages || newIsStartDebugFrom) && launches.length > 1) {
final DialogResultHolder resultHolder = new DialogResultHolder();
Display.getDefault().syncExec(new Runnable() {
public void run() {
// TODO - Advanced message dialog with 'don't show this
// again' check.
if (fHasContiniousLaunch) {
resultHolder.setResult(MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_confirmation,
PHPDebugCoreMessages.PHPLaunchUtilities_0));
} else {
if (newIsDebugAllPages) {
resultHolder.setResult(MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_confirmation,
PHPDebugCoreMessages.PHPLaunchUtilities_7));
} else {
// newIsStartDebugFrom == true
resultHolder.setResult(MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
PHPDebugCoreMessages.PHPLaunchUtilities_confirmation,
PHPDebugCoreMessages.PHPLaunchUtilities_8));
}
}
if (resultHolder.getResult()) {
// disable the auto remove launches for the next launch
PHPDebugPlugin.setDisableAutoRemoveLaunches(true);
// manually remove the old launches and continue this
// launch
removeAndTerminateOldLaunches(newLaunch);
} else {
// Remove the latest launch
DebugPlugin.getDefault().getLaunchManager().removeLaunch(newLaunch);
}
}
});
return resultHolder.getResult();
} else {
if (newIsDebugAllPages || newIsStartDebugFrom) {
PHPDebugPlugin.setDisableAutoRemoveLaunches(true);
} else {
// There are no other launches AND the new launch doesn't have a
// debug-all-pages.
PHPDebugPlugin
.setDisableAutoRemoveLaunches(!PHPDebugPlugin.getDefault().getInitialAutoRemoveLaunches());
// This will manually remove the old launches if needed
DebugUIPlugin.getDefault().getLaunchConfigurationManager().launchAdded(newLaunch);
}
return true;
}
}
// In case that there are no launches, make sure to enable the auto-remove
// old launches in case it's needed
private static void checkAutoRemoveLaunches() {
if (DebugPlugin.getDefault().getLaunchManager().getLaunches().length == 1) {
PHPDebugPlugin.setDisableAutoRemoveLaunches(false);
}
}
/**
* Returns if the given launch configuration holds an attribute for 'debug
* all pages'.
*
* @param launchConfiguration
* An {@link ILaunchConfiguration}
* @return True, if the configuration holds an attribute for 'debug all
* pages'.
* @throws CoreException
*/
public static boolean isDebugAllPages(ILaunch launch) throws CoreException {
String attribute = launch.getAttribute(IPHPDebugConstants.DEBUGGING_PAGES);
return attribute != null && attribute.equals(IPHPDebugConstants.DEBUGGING_ALL_PAGES);
}
/**
* Returns if the given launch configuration holds an attribute for 'start
* debug from'.
*
* @param launchConfiguration
* An {@link ILaunchConfiguration}
* @return True, if the configuration holds an attribute for 'start debug
* from'.
* @throws CoreException
*/
public static boolean isStartDebugFrom(ILaunch launch) throws CoreException {
String attribute = launch.getAttribute(IPHPDebugConstants.DEBUGGING_PAGES);
return attribute != null && attribute.equals(IPHPDebugConstants.DEBUGGING_START_FROM);
}
// terminate and remove all the existing launches accept for the given new
// launch.
private static void removeAndTerminateOldLaunches(ILaunch newLaunch) {
ILaunchManager lManager = DebugPlugin.getDefault().getLaunchManager();
Object[] launches = lManager.getLaunches();
for (Object element : launches) {
ILaunch launch = (ILaunch) element;
if (launch != newLaunch) {
if (!launch.isTerminated()) {
try {
launch.terminate();
} catch (DebugException e) {
Logger.logException(e);
}
}
lManager.removeLaunch(launch);
}
}
}
/**
* Display a wait window, indicating the user that the debug session is in
* progress and the PDT is waiting for the debugger's response. Once a
* response arrives, the {@link #hideWaitForDebuggerMessage()} should be
* called to remove the window. In case a response does not arrive, there is
* a good chance that the {@link #showLaunchErrorMessage()} should be
* called.
*
* @param debugConnection
* @see #hideWaitForDebuggerMessage()
* @see #showLaunchErrorMessage()
*/
public static void showWaitForDebuggerMessage(final DebugConnection debugConnection) {
if (progressDialog != null) {
// Allow only one progress indicator
return;
}
Display.getDefault().asyncExec(new Runnable() {
public void run() {
progressDialog = new DebuggerDelayProgressMonitorDialog();
if (progressDialog.open() == Window.CANCEL) {
debugConnection.disconnect();
}
progressDialog = null;
}
});
}
/**
* Hides the progress indicator that appears when user is waiting for the
* debugger to response.
*
* @see #showWaitForDebuggerMessage()
*/
public static void hideWaitForDebuggerMessage() {
if (progressDialog != null) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
if (progressDialog != null) {
progressDialog.close();
}
}
});
progressDialog = null;
}
}
/**
* Display a standard error message to indicating an fatal error detected
* while staring a debug session. A fatal error occurs when the remote
* debugger does not exist or has a different version.
*/
public static void showLaunchErrorMessage() {
showDebuggerErrorMessage(PHPDebugCoreMessages.Debugger_Launch_Error,
PHPDebugCoreMessages.Debugger_Error_Message);
}
/**
* Display an error message to indicating an fatal error detected while
* staring a debug session. A fatal error occurs when the remote debugger
* does not exist or has a different version.
*
* @param errorMessage
* The message to display.
*/
public static void showLaunchErrorMessage(final String errorMessage) {
showDebuggerErrorMessage(PHPDebugCoreMessages.Debugger_Launch_Error, errorMessage);
}
/**
* Display an error message to indicating an fatal error detected while
* staring a debug session. A fatal error occurs when the remote debugger
* does not exist or has a different version.
*
* @param title
* The error message title.
* @param errorMessage
* The message to display.
*/
public static void showDebuggerErrorMessage(final String title, final String errorMessage) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
MessageDialog.openError(Display.getDefault().getActiveShell(), title, errorMessage);
}
});
}
/*
* A class used to hold the message dialog results
*/
private static class DialogResultHolder {
private int returnCode;
private boolean result;
public boolean getResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public int getReturnCode() {
return returnCode;
}
public void setReturnCode(int returnCode) {
this.returnCode = returnCode;
}
}
private static class DebuggerDelayProgressMonitorDialog extends ProgressMonitorDialog {
public DebuggerDelayProgressMonitorDialog() {
super(null);
setBlockOnOpen(true);
setCancelable(true);
}
protected void createCancelButton(Composite parent) {
cancel = createButton(parent, IDialogConstants.CANCEL_ID, PHPDebugCoreMessages.PHPLaunchUtilities_terminate,
true);
if (arrowCursor == null) {
arrowCursor = new Cursor(cancel.getDisplay(), SWT.CURSOR_ARROW);
}
cancel.setCursor(arrowCursor);
setOperationCancelButtonEnabled(enableCancelButton);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.dialogs.ProgressMonitorDialog#createDialogArea(
* org.eclipse.swt.widgets.Composite)
*/
protected Control createDialogArea(Composite parent) {
Control c = super.createDialogArea(parent);
getProgressMonitor().beginTask(PHPDebugCoreMessages.PHPLaunchUtilities_waitingForDebugger,
IProgressMonitor.UNKNOWN);
return c;
}
}
/**
* Opens the launch configuration dialog on the given launch configuration
* in the given mode.
*
* @param configuration
* An {@link ILaunchConfiguration}
* @param mode
* The launch mode (Run/Debug)
*/
public static void openLaunchConfigurationDialog(final ILaunchConfiguration configuration, final String mode) {
// Run it on the UI thread
Display.getDefault().syncExec(new Runnable() {
public void run() {
ILaunchConfiguration conf = configuration;
try {
// The DebugUIPlugin creates stand-in launches with copied
// configurations
// while a launch is waiting for a build. These copied
// configurations
// have an attribute that points to the config that the user
// is really
// launching.
String underlyingHandle = configuration.getAttribute(DebugUIPlugin.ATTR_LAUNCHING_CONFIG_HANDLE,
""); //$NON-NLS-1$
if (underlyingHandle.length() > 0) {
ILaunchConfiguration underlyingConfig = DebugPlugin.getDefault().getLaunchManager()
.getLaunchConfiguration(underlyingHandle);
if (underlyingConfig != null) {
conf = underlyingConfig;
}
}
} catch (CoreException e) {
}
ILaunchGroup group = DebugUITools.getLaunchGroup(conf, mode);
if (group != null) {
DebugUITools.openLaunchConfigurationDialog(Display.getDefault().getActiveShell(), conf,
group.getIdentifier(), null);
}
}
});
}
/**
* Returns an array of system environment attributes from the given launch
* configuration. If empty, then the current native environment attributes
* will be returned. From this we append any additional environment
* variables we might want to add. Note: Additional environments may
* override the native environment attributes, but disregarded when an
* equivalent launch configuration attribute is set for the given launch.
*
* @param configuration
* the launch configuration
* @param additionalEnv
* additional environment strings
* @return the complete environment
* @throws CoreException
* rethrown exception
*/
@SuppressWarnings("unchecked")
public static String[] getEnvironment(ILaunchConfiguration configuration, String[] additionalEnv)
throws CoreException {
if (additionalEnv == null) {
additionalEnv = new String[0];
}
Map<String, String> additionalEnvMap = asAttributesMap(additionalEnv);
String[] totalEnv = null;
String[] launchConfigurationEnvironment = DebugPlugin.getDefault().getLaunchManager()
.getEnvironment(configuration);
if (launchConfigurationEnvironment != null) {
// The launch configuration tab has environment settings.
Map<String, String> envMap = asAttributesMap(launchConfigurationEnvironment);
// Make sure that these settings override any additional settings,
// so add them to the
// additional environments map.
envMap.putAll(additionalEnvMap);
totalEnv = asAttributesArray(envMap);
} else {
// We have nothing in the environment tab, so we need to set
// currentEnv ourselves to the current environment
Map<String, String> nativeEnvironment = DebugPlugin.getDefault().getLaunchManager()
.getNativeEnvironmentCasePreserved();
// Make sure we override any native environment with the additional
// environment values
nativeEnvironment.putAll(additionalEnvMap);
totalEnv = asAttributesArray(nativeEnvironment);
}
return totalEnv;
}
/*
* Returns a map of Strings parsed from a given array of attributes in a
* form of 'key=value'.
*/
public static Map<String, String> asAttributesMap(String[] attributesArray) {
Map<String, String> map = new HashMap<String, String>();
if (attributesArray == null) {
return map;
}
for (String attribute : attributesArray) {
try {
if (attribute != null) {
int index = attribute.indexOf('=');
map.put(attribute.substring(0, index), attribute.substring(index + 1));
}
} catch (Exception e) {
Logger.logException("Error while parsing launch attribute '" //$NON-NLS-1$
+ attribute + '\'', e);
}
}
return map;
}
/*
* Returns an array of Strings in the form of 'key=value'
*/
public static String[] asAttributesArray(Map<String, String> attributesMap) {
String[] attributes = new String[attributesMap.size()];
int index = 0;
for (Map.Entry<String, String> entry : attributesMap.entrySet()) {
attributes[index++] = entry.getKey() + '=' + entry.getValue();
}
return attributes;
}
/**
* Returns PHP CGI related parameters needed for launch
*
* @param fileName
* @param query
* @param phpConfigDir
* @param phpExeDir
* @return A map of environment settings.
*/
public static Map<String, String> getPHPCGILaunchEnvironment(String fileName, String query, String phpConfigDir,
String phpExeDir, String[] scriptArguments) {
Map<String, String> env = new HashMap<String, String>();
env.put("REQUEST_METHOD", "GET"); //$NON-NLS-1$ //$NON-NLS-2$
env.put("SCRIPT_FILENAME", fileName); //$NON-NLS-1$
env.put("SCRIPT_NAME", fileName); //$NON-NLS-1$
env.put("PATH_TRANSLATED", fileName); //$NON-NLS-1$
env.put("PATH_INFO", fileName); //$NON-NLS-1$
// Build query string
StringBuilder queryStringBuf = new StringBuilder(query);
queryStringBuf.append("&debug_host=127.0.0.1"); //$NON-NLS-1$
if (scriptArguments != null) {
for (String arg : scriptArguments) {
queryStringBuf.append('&').append(arg);
}
}
env.put("QUERY_STRING", queryStringBuf.toString()); //$NON-NLS-1$
env.put("REDIRECT_STATUS", "1"); //$NON-NLS-1$ //$NON-NLS-2$
env.put("PHPRC", phpConfigDir); //$NON-NLS-1$
appendLibrarySearchPathEnv(env, new File(phpExeDir));
return env;
}
/**
* Finds and returns PHP server that corresponds to provided launch
* configuration.
*
* @param configuration
* @return PHP server that corresponds to provided launch configuration
* @throws CoreException
*/
public static Server getPHPServer(ILaunchConfiguration configuration) throws CoreException {
String serverName = configuration.getAttribute(Server.NAME, ""); //$NON-NLS-1$
return ServersManager.getServer(serverName);
}
/**
* Finds and returns PHP exe item that corresponds to provided launch
* configuration.
*
* @param configuration
* @return PHP exe item that corresponds to provided launch configuration
* @throws CoreException
*/
public static PHPexeItem getPHPExe(ILaunchConfiguration configuration) throws CoreException {
PHPexeItem item = null;
String path = configuration.getAttribute(PHPRuntime.PHP_CONTAINER, (String) null);
if (path == null) {
IProject project = null;
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
String projectName = configuration.getAttribute(IPHPDebugConstants.PHP_Project, (String) null);
if (projectName != null) {
project = workspaceRoot.getProject(projectName);
} else {
String phpScriptString = configuration.getAttribute(IPHPDebugConstants.ATTR_FILE, (String) null);
if (phpScriptString != null) {
IPath filePath = new Path(phpScriptString);
IResource scriptRes = workspaceRoot.findMember(filePath);
if (scriptRes != null) {
project = scriptRes.getProject();
}
}
}
item = PHPDebugPlugin.getPHPexeItem(project);
} else {
IPath exePath = Path.fromPortableString(path);
org.eclipse.php.core.PHPVersion version = PHPRuntime.getPHPVersion(exePath);
if (version == null) {
String exeName = exePath.lastSegment();
item = PHPexes.getInstance().getItem(exeName);
} else {
item = PHPDebugPlugin.getPHPexeItem(version);
}
}
return item;
}
public static Map<String, String> getPHP54BuildinServerLaunchEnvironment(String fileName, String query,
String phpConfigDir, String phpExeDir, String[] scriptArguments) {
Map<String, String> env = new HashMap<String, String>();
env.put("REQUEST_METHOD", "GET"); //$NON-NLS-1$ //$NON-NLS-2$
// env.put("SCRIPT_FILENAME", fileName);
// env.put("SCRIPT_NAME", fileName);
// env.put("PATH_TRANSLATED", fileName);
// env.put("PATH_INFO", fileName);
// Build query string
StringBuilder queryStringBuf = new StringBuilder(query);
queryStringBuf.append("&debug_host=127.0.0.1"); //$NON-NLS-1$
if (scriptArguments != null) {
for (String arg : scriptArguments) {
queryStringBuf.append('&').append(arg);
}
}
env.put("QUERY_STRING", queryStringBuf.toString()); //$NON-NLS-1$
env.put("REDIRECT_STATUS", "1"); //$NON-NLS-1$ //$NON-NLS-2$
env.put("PHPRC", phpConfigDir); //$NON-NLS-1$
appendLibrarySearchPathEnv(env, new File(phpExeDir));
return env;
}
/**
* Appends needed environment variable that says where to look for 3rd party
* libraries depending on the OS.
*
* @param env
* Hash map to append environment variable to
* @param phpExeDir
* Directory handle where PHP.exe is located
*/
public static void appendLibrarySearchPathEnv(Map<String, String> env, File phpExeDir) {
String variable = getLibrarySearchEnvVariable();
if (variable == null) {
return;
}
String value = getLibrarySearchEnvValue(variable, phpExeDir, false);
env.put(variable, value);
}
/**
* Append PHP executable at the beginning of PATH env variable
*
* @param env
* Hash map to append environment variable to
* @param phpExe
* php executable location
*/
public static void appendExecutableToPathEnv(Map<String, String> env, File phpExeDir) {
String phpPath = phpExeDir.getPath();
if (phpPath == null || phpPath.isEmpty()) {
return;
}
String pathKey = getPathEnvKey(env);
String path = env.get(pathKey);
if (path == null) {
path = phpPath;
} else {
path = phpPath + File.pathSeparatorChar + path;
}
env.put(pathKey, path);
}
private static String getPathEnvKey(Map<String, String> env) {
String pathKey = "PATH"; //$NON-NLS-1$
for (String key : env.keySet()) {
if ("path".equalsIgnoreCase(key)) { //$NON-NLS-1$
pathKey = key;
}
}
return pathKey;
}
/**
* Returns needed environment variable that says where to look for 3rd party
* libraries depending on the OS.
*
* @param phpExeDir
* Directory handle where PHP.exe is located
* @return string containing variable=value for appending it to the process
* environment vars array
*/
public static String getLibrarySearchPathEnv(File phpExeDir, boolean quoted) {
String variable = getLibrarySearchEnvVariable();
if (variable == null) {
return null;
}
String value = getLibrarySearchEnvValue(variable, phpExeDir, quoted);
return new StringBuilder(variable).append('=').append(value).toString();
}
public static String getLibrarySearchPathEnv(File phpExeDir) {
return getLibrarySearchPathEnv(phpExeDir, false);
}
private static String getLibrarySearchEnvValue(String variable, File phpExeDir, boolean quoted) {
StringBuilder buf = new StringBuilder();
// opening quote
if (quoted) {
buf.append('"');
}
File libDirectory = new File(phpExeDir.getParentFile(), "lib"); //$NON-NLS-1$
if (libDirectory.exists()) {
buf.append(libDirectory.getAbsolutePath());
} else {
buf.append(phpExeDir.getAbsolutePath());
}
try {
String env = System.getenv(variable);
if (env != null) {
buf.append(File.pathSeparatorChar).append(env);
}
} catch (Throwable e) {
}
// closing quote
if (quoted) {
buf.append('"');
}
return buf.toString();
}
private static String getLibrarySearchEnvVariable() {
String os = Platform.getOS();
if (Platform.OS_WIN32.equals(os)) {
return null; // "PATH";
}
if (Platform.OS_MACOSX.equals(os)) {
return "DYLD_FALLBACK_LIBRARY_PATH"; //$NON-NLS-1$
}
return "LD_LIBRARY_PATH"; //$NON-NLS-1$
}
/**
* Creates and returns a command line invocation string for the execution of
* the PHP script.
*
* @param configuration
* Launch configuration
* @param phpExe
* PHP Executable path
* @param phpConfigDir
* PHP configuration file location (directory where php.ini
* resides)
* @param scriptPath
* Script path
* @param phpIniLocation
* PHP configuration file path
* @param args
* Command line arguments, if using PHP CLI, otherwise -
* <code>null</code>
* @param phpVersion
* @return commands array
* @throws CoreException
*/
public static String[] getCommandLine(ILaunchConfiguration configuration, String phpExe, String phpConfigDir,
String scriptPath, String[] args, String phpVersion) throws CoreException {
// Check if we should treat ASP tags as PHP tags
IProject project = getProject(configuration);
String aspTags = ProjectOptions.isSupportingASPTags(project) ? "on" //$NON-NLS-1$
: "off"; //$NON-NLS-1$
String shortOpenTag = ProjectOptions.useShortTags(project) ? "on" //$NON-NLS-1$
: "off"; //$NON-NLS-1$
boolean builtIn = false;
final PHPexeItem[] phpItems = PHPexes.getInstance().getAllItems();
for (PHPexeItem item : phpItems) {
if (item.getExecutable().getAbsolutePath().equals(phpExe)) {
builtIn = !item.isEditable() || !item.isLoadDefaultINI();
break;
}
}
List<String> cmdLineList = new LinkedList<String>();
if (builtIn) {
cmdLineList.addAll(Arrays.asList(new String[] { phpExe, "-n", "-c", //$NON-NLS-1$ //$NON-NLS-2$
phpConfigDir, "-d", "asp_tags=" + aspTags, "-d", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
"short_open_tag=" + shortOpenTag, scriptPath })); //$NON-NLS-1$
} else {
cmdLineList.addAll(Arrays.asList(new String[] { phpExe, "-c", //$NON-NLS-1$
phpConfigDir, "-d", "asp_tags=" + aspTags, "-d", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
"short_open_tag=" + shortOpenTag, scriptPath })); //$NON-NLS-1$
}
if (args != null) {
cmdLineList.addAll(Arrays.asList(args));
}
return cmdLineList.toArray(new String[cmdLineList.size()]);
}
public static String[] getCommandLine(ILaunchConfiguration configuration, String phpExe, String phpConfigDir,
String scriptPath, String[] args) throws CoreException {
return getCommandLine(configuration, phpExe, phpConfigDir, scriptPath, args, null);
}
public static String[] getCommandLineForPHP54BuildinServer(ILaunchConfiguration configuration, String phpExe,
String phpConfigDir, String server, String root, String routerFile, String[] args, boolean useDefaultPHPIni)
throws CoreException {
// Check if we should treat ASP tags as PHP tags
IProject project = getProject(configuration);
String aspTags = ProjectOptions.isSupportingASPTags(project) ? "on" //$NON-NLS-1$
: "off"; //$NON-NLS-1$
String shortOpenTag = ProjectOptions.useShortTags(project) ? "on" //$NON-NLS-1$
: "off"; //$NON-NLS-1$
if (server.startsWith("http://")) { //$NON-NLS-1$
server = server.substring(7);
} else if (server.startsWith("https://")) { //$NON-NLS-1$
server = server.substring(8);
}
List<String> cmdLineList = new LinkedList<String>();
if (routerFile == null) {
cmdLineList.addAll(Arrays.asList(new String[] { phpExe, "-S", //$NON-NLS-1$
server, "-t", root, useDefaultPHPIni ? "" : "-n", "-c", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
phpConfigDir, "-d", //$NON-NLS-1$
"asp_tags=" + aspTags, "-d", //$NON-NLS-1$ //$NON-NLS-2$
"short_open_tag=" + shortOpenTag })); //$NON-NLS-1$
} else {
cmdLineList.addAll(Arrays.asList(new String[] { phpExe, "-S", //$NON-NLS-1$
server, "-t", root, routerFile, //$NON-NLS-1$
useDefaultPHPIni ? "" : "-n", "-c", phpConfigDir, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
"-d", "asp_tags=" + aspTags, "-d", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
"short_open_tag=" + shortOpenTag })); //$NON-NLS-1$
}
if (args != null) {
cmdLineList.addAll(Arrays.asList(args));
}
return cmdLineList.toArray(new String[cmdLineList.size()]);
}
public static String[] getCommandLineForPHP54BuildinServer(ILaunchConfiguration configuration, String phpExe,
String phpConfigDir, String server, String root, String routerFile, String[] args) throws CoreException {
return getCommandLineForPHP54BuildinServer(configuration, phpExe, phpConfigDir, server, root, routerFile, args,
false);
}
/**
* Returns the project that is related to given debug target or
* <code>null</code> if there is no any.
*
* @param target
* @return project that is related to given debug target or
* <code>null</code> if there is no any
*/
public static IProject getProject(IDebugTarget target) {
ILaunch launch = target.getLaunch();
if (launch == null) {
return null;
}
ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
if (launchConfiguration == null) {
return null;
}
return getProject(launchConfiguration);
}
/**
* Returns the project that is related to the launch configuration.
*
* @param configuration
* @return
*/
private static IProject getProject(ILaunchConfiguration configuration) {
try {
String projectName = configuration.getAttribute(IPHPDebugConstants.PHP_Project, (String) null);
if (projectName != null) {
return ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
}
String fileNameString = configuration.getAttribute(IPHPDebugConstants.ATTR_FILE, (String) null);
if (fileNameString == null) {
return null;
}
final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
final IPath filePath = new Path(fileNameString);
IResource res = workspaceRoot.findMember(filePath);
if (res != null) {
return res.getProject();
}
} catch (CoreException ce) {
Logger.logException(ce);
}
return null;
}
/**
* Returns the program arguments from the launch configuration. Program
* arguments will allow variable substitution as well. The arguments are
* extracted from the IDebugParametersKeys.EXE_CONFIG_PROGRAM_ARGUMENTS
* configuration attribute.
*
* @param configuration
* the launch configuration
* @return the program arguments
* @throws CoreException
* rethrown exception
*/
public static String[] getProgramArguments(ILaunchConfiguration configuration) throws CoreException {
String arguments = configuration.getAttribute(IDebugParametersKeys.EXE_CONFIG_PROGRAM_ARGUMENTS, (String) null);
if (arguments == null || arguments.trim().equals("")) { //$NON-NLS-1$
return new String[0];
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=298606
return DebugPlugin.parseArguments(
VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(arguments));
}
/**
* Returns true if the given project is using ASP tags as PHP tags.
*
* @param project
* an {@link IProject}.
* @return True, if ASP tags are supported, false otherwise.
*/
public static boolean isUsingASPTags(IProject project) {
PreferencesSupport preferencesSupport = new PreferencesSupport(PHPCorePlugin.getPluginId());
String value = preferencesSupport.getPreferencesValue(CorePreferenceConstants.Keys.EDITOR_USE_ASP_TAGS, null,
project);
if (value == null) {
value = preferencesSupport.getWorkspacePreferencesValue(CorePreferenceConstants.Keys.EDITOR_USE_ASP_TAGS);
}
return Boolean.valueOf(value).booleanValue();
}
/**
* Generates debug query from parameters for the GET method. This method
* encodes debug parameters.
*
* @param launch
* @return
*/
public static String generateQuery(ILaunch launch, IDebugParametersInitializer debugParametersInitializer) {
StringBuilder buf = new StringBuilder();
Hashtable<String, String> parameters = debugParametersInitializer.getDebugParameters(launch);
Enumeration<String> e = parameters.keys();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
buf.append(key).append('=');
try {
buf.append(URLEncoder.encode((String) parameters.get(key), "UTF-8")); //$NON-NLS-1$
} catch (UnsupportedEncodingException exc) {
}
if (e.hasMoreElements()) {
buf.append('&'); // $NON-NLS-1$
}
}
return buf.toString();
}
/*
* Tunneling functionality
*/
/**
* Returns a SSHTunnel instance in case defined in the given launch
* configuration. The returned SSHTunnel may be null in case the given
* configuration is not defined to use one. Also, the returned instance
* might be shared between other launches as well, and might already be in a
* connected state.
*
* @param configuration
* @return An SSHTunnel instance; Null, in case the configuration does not
* need one.
*/
public static SSHTunnel getSSHTunnel(ILaunchConfiguration configuration) {
try {
if (configuration.getAttribute(IPHPDebugConstants.USE_SSH_TUNNEL, false)) {
String remoteHost = PHPLaunchUtilities.getDebugHost(configuration);
int port = PHPLaunchUtilities.getDebugPort(configuration);
if (remoteHost != null && remoteHost.length() > 0 && port > -1) {
String userName = configuration.getAttribute(IPHPDebugConstants.SSH_TUNNEL_USER_NAME, "");//$NON-NLS-1$
String password = PHPLaunchUtilities.getSecurePreferences(remoteHost).get(userName, "");//$NON-NLS-1$
return SSHTunnelFactory.getSSHTunnel(remoteHost, userName, password, port, port);
}
}
} catch (CoreException e) {
Logger.logException("Error obtaining an SSHTunnel instance", e);//$NON-NLS-1$
} catch (StorageException e) {
Logger.logException("Error accessing the secured storage for the debug SSH tunnel", //$NON-NLS-1$
e);
}
return null;
}
/**
* Returns the port that is associated to the debugger that is involved in
* the given launch configuration.
*
* @return The port in use. -1, in case of an error.
*/
public static int getDebugPort(ILaunchConfiguration launchConfiguration) {
int port = -1;
try {
String debuggerID = launchConfiguration.getAttribute(PHPDebugCorePreferenceNames.PHP_DEBUGGER_ID,
PHPDebugPlugin.getCurrentDebuggerId());
IDebuggerConfiguration debuggerConfiguration = PHPDebuggersRegistry.getDebuggerConfiguration(debuggerID);
port = debuggerConfiguration.getPort();
Server server = ServersManager.getServer(launchConfiguration.getAttribute(Server.NAME, "")); //$NON-NLS-1$
int customPort = -1;
// Check custom port for server's Zend Debugger
if (ZendDebuggerConfiguration.ID.equals(debuggerID)) {
if (server != null) {
customPort = ZendDebuggerSettingsUtil.getDebugPort(server.getUniqueId());
}
}
// Check custom port for server's XDebug
else if (XDebugDebuggerConfiguration.ID.equals(debuggerID)) {
if (server != null) {
customPort = XDebugDebuggerSettingsUtil.getDebugPort(server.getUniqueId());
}
}
if (customPort != -1)
port = customPort;
} catch (Exception e) {
Logger.logException("Could not retrieve the debugger's port number", //$NON-NLS-1$
e);
}
return port;
}
/**
* Returns the host that is associated with the given launch configuration.
* The returned host can be null in case of an error or a missing host
* setting.
*
* @return The host address, or null.
*/
public static String getDebugHost(ILaunchConfiguration launchConfiguration) {
try {
String url = launchConfiguration.getAttribute(Server.BASE_URL, "");//$NON-NLS-1$
if (url == null || url.length() == 0) {
return null;
}
return new URL(url).getHost();
} catch (CoreException e) {
Logger.logException("Could not retrieve the host name", e);//$NON-NLS-1$
} catch (MalformedURLException e) {
Logger.logException("Could not retrieve the host name", e);//$NON-NLS-1$
}
return null;
}
/**
* Returns the secure storage node for the tunnel connections that are set
* on the given host.
*
* @param host
*
* @return An ISecurePreferences for the php debug secured node.
*/
public static ISecurePreferences getSecurePreferences(String host) {
String hostPath = "";//$NON-NLS-1$
if (host != null) {
hostPath = '/' + host;
}
ISecurePreferences root = SecurePreferencesFactory.getDefault();
ISecurePreferences node = root.node(IPHPDebugConstants.SSH_TUNNEL_SECURE_PREF_NODE + hostPath);
return node;
}
/**
* Checks if given port is not already occupied.
*
* @param port
* @return <code>true</code> if given port number is not already occupied,
* <code>false</code> otherwise
*/
public static boolean isPortAvailable(int port) {
ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(port);
ss.setReuseAddress(true);
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return true;
} catch (IOException e) {
} finally {
if (ds != null) {
ds.close();
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
/* should not be thrown */
}
}
}
return false;
}
/**
* Checks if there is any running debug daemon available for given debugger
* type and port number.
*
* @param port
* @param debuggerId
* @return <code>true</code> if there is running debug daemon,
* <code>false</code> otherwise
*/
public static boolean isDebugDaemonActive(int port, String debuggerId) {
return DaemonPlugin.getDefault().validateCommunicationDaemons(debuggerId, port);
}
/**
* Checks if given launch configurations is of provided type.
*
* @param launchConfiguration
* @param launchConfigurationType
* @return <code>true</code> if given launch configurations is of provided
* type, <code>false</code> otherwise
*/
public static boolean isLaunchConfigurationTypeOf(ILaunchConfiguration launchConfiguration,
String launchConfigurationType) {
ILaunchConfigurationType type = DebugPlugin.getDefault().getLaunchManager()
.getLaunchConfigurationType(launchConfigurationType);
try {
if (launchConfiguration.getType().equals(type)) {
return true;
}
} catch (CoreException e) {
// Should not happened, anyway...
Logger.logException(e);
}
return false;
}
}