/******************************************************************************* * Copyright (c) 2009 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 *******************************************************************************/ package org.eclipse.php.internal.debug.core.zend.debugger; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.IBuildpathContainer; import org.eclipse.dltk.core.IBuildpathEntry; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.environment.EnvironmentPathUtils; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.php.debug.core.debugger.IDebugHandler; import org.eclipse.php.debug.core.debugger.messages.IDebugMessage; import org.eclipse.php.debug.core.debugger.messages.IDebugNotificationMessage; import org.eclipse.php.debug.core.debugger.messages.IDebugRequestMessage; import org.eclipse.php.debug.core.debugger.messages.IDebugResponseMessage; import org.eclipse.php.debug.core.debugger.parameters.IDebugParametersKeys; import org.eclipse.php.internal.core.includepath.IncludePath; import org.eclipse.php.internal.core.includepath.IncludePathManager; import org.eclipse.php.internal.core.util.FileUtils; import org.eclipse.php.internal.core.util.PHPSearchEngine; import org.eclipse.php.internal.core.util.PHPSearchEngine.ExternalFileResult; import org.eclipse.php.internal.core.util.PHPSearchEngine.IncludedFileResult; import org.eclipse.php.internal.core.util.PHPSearchEngine.ResourceResult; import org.eclipse.php.internal.core.util.PHPSearchEngine.Result; import org.eclipse.php.internal.debug.core.*; import org.eclipse.php.internal.debug.core.launching.DebugSessionIdGenerator; import org.eclipse.php.internal.debug.core.pathmapper.*; 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.PHPProjectPreferences; import org.eclipse.php.internal.debug.core.zend.communication.*; import org.eclipse.php.internal.debug.core.zend.debugger.messages.*; import org.eclipse.php.internal.debug.core.zend.debugger.parameters.DefaultDebugParametersInitializer; import org.eclipse.php.internal.debug.core.zend.model.PHPDebugTarget; import org.eclipse.swt.widgets.Display; /** * An IRemoteDebugger implementation. */ public class RemoteDebugger implements IRemoteDebugger { private static class RemoteFileQuery { /** * Generates and return query URL. * * @param url * @param isProfile * @param launchConfiguration * @return query URL */ public static String generateQuery(String url) { StringBuffer buffer = new StringBuffer(url); if (url.indexOf("?") == -1) { //$NON-NLS-1$ buffer.append('?'); } else { if (!url.endsWith("&")) { buffer.append('&'); } } Map<String, String> parameters = new Hashtable<String, String>(); parameters.put(DefaultDebugParametersInitializer.START_DEBUG, "1"); //$NON-NLS-1$ parameters.put(DefaultDebugParametersInitializer.SEND_SESS_END, "1"); //$NON-NLS-1$ parameters.put(DefaultDebugParametersInitializer.DEBUG_NO_CACHE, Long.toString(System.currentTimeMillis())); parameters.put(DefaultDebugParametersInitializer.ORIGINAL_URL, url); parameters.put(DefaultDebugParametersInitializer.DEBUG_SESSION_ID, String.valueOf(DebugSessionIdGenerator.generateSessionID())); parameters.put(DefaultDebugParametersInitializer.DEBUG_NO_REMOTE, "1"); //$NON-NLS-1$ parameters.put(DefaultDebugParametersInitializer.DEBUG_FASTFILE, "1"); //$NON-NLS-1$ parameters.put(DefaultDebugParametersInitializer.ZRAY_DISABLE, "1"); //$NON-NLS-1$ parameters.put(DefaultDebugParametersInitializer.IS_DEBUG_URL, "1"); //$NON-NLS-1$ boolean useSSL = InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID) .getBoolean(PHPDebugCorePreferenceNames.ZEND_DEBUG_ENCRYPTED_SSL_DATA, false); if (useSSL) { parameters.put(DefaultDebugParametersInitializer.USE_SSL, "1"); //$NON-NLS-1$ } Iterator<String> it = parameters.keySet().iterator(); while (it.hasNext()) { String key = it.next(); buffer.append(key).append('='); try { buffer.append(URLEncoder.encode(parameters.get(key), "UTF-8")); } catch (UnsupportedEncodingException e) { } if (it.hasNext()) { buffer.append('&'); } } return buffer.toString(); } } /** * Original PDT protocol ID from 04/2006 */ public static final int PROTOCOL_ID_2006040701 = 2006040701; /** * Improved protocol ID from 06/2007 which provides new message type ( * {@link StartProcessFileNotification}) that allows to control debug state * when debugger is preparing processing new file. We use this state for * doing on-demand path mapping, and for sending breakpoints for the next * file. */ public static final int PROTOCOL_ID_2006040703 = 2006040703; /** * New protocol ID from 04/2008 which provides two new message types: * {@link GetCWDRequest} allows to ask Debugger to return current working * directory, */ public static final int PROTOCOL_ID_2006040705 = 2006040705; /** * New protocol ID from 12/2012 which provides new message type: * {@link AddFilesRequest} allows send initial list of files which contain * at least one breakpoint. */ public static final int PROTOCOL_ID_2012121702 = 2012121702; /** * Latest protocol ID */ public static final int PROTOCOL_ID_LATEST = PROTOCOL_ID_2012121702; // ========================== commercial debug protocols: // =================================== /** * Original commercial debug protocol ID from 04/2006 which changes from * original PDT protocol by the {@link FileContentRequest} message which * allows to send file contents over the debug connection to/from debugger. */ public static final int COMMERCIAL_PROTOCOL_ID_2006040901 = 2006040901; /** * Improved protocol ID from 06/2007 which provides new message type ( * {@link StartProcessFileNotification}) that allows to control debug state * when debugger is preparing processing new file. We use this state for * doing on-demand path mapping, and for sending breakpoints for the next * file. */ public static final int COMMERCIAL_PROTOCOL_ID_2006040903 = 2006040903; /** * New protocol ID from 04/2008 which provides two new message types: * {@link GetCWDRequest} allows to ask Debugger to return current working * directory, */ public static final int COMMERCIAL_PROTOCOL_ID_2006040905 = 2006040905; /** * New protocol ID from 12/2012 which provides new message type: * {@link AddFilesRequest} allows send initial list of files which contain * at least one breakpoint. */ public static final int COMMERCIAL_PROTOCOL_ID_2012121702 = 2012121702; /** * Should always point to the latest commercial I5 protocol */ public static final int COMMERCIAL_PROTOCOL_ID_LATEST = COMMERCIAL_PROTOCOL_ID_2012121702; // ========================== i5-OS protocols: // =================================== /** * Original commercial debug protocol ID for Debugger i5-edition from * 04/2006 (parallel to COMMERCIAL_PROTOCOL_ID_2006040901) */ public static final int COMMERCIAL_I5_PROTOCOL_ID_2006040902 = 2006040902; /** * This is the protocol ID for Debugger i5-edition from 06/2007 (parallel to * COMMERCIAL_PROTOCOL_ID_2006040903) */ public static final int COMMERCIAL_I5_PROTOCOL_ID_2006040904 = 2006040904; /** * This is the protocol ID for Debugger i5-edition from 04/2008 (parallel to * COMMERCIAL_PROTOCOL_ID_2006040905) */ public static final int COMMERCIAL_I5_PROTOCOL_ID_2006040906 = 2006040906; /** * Should always point to the latest commercial I5 protocol */ public static final int COMMERCIAL_I5_PROTOCOL_ID_LATEST = COMMERCIAL_I5_PROTOCOL_ID_2006040906; private static final String EVAL_ERROR = "[Error]"; //$NON-NLS-1$ protected boolean isDebugMode = System.getProperty("loggingDebug") != null; //$NON-NLS-1$ private DebugConnection connection; private IDebugHandler debugHandler; private Map<String, String> resolvedFiles; private Map<String, List<IPath>> resolvedIncludePaths; private int currentProtocolId = 0; private int previousSuspendCount; private String cachedCWD; private PHPstack cachedStack; private String phpVersion; /** * Creates new RemoteDebugSession */ public RemoteDebugger(IDebugHandler debugHandler, DebugConnection connection) { // this.kit = createCommunicationKit(); this.connection = connection; this.debugHandler = debugHandler; connection.setCommunicationAdministrator(this); connection.setCommunicationClient(this); resolvedFiles = new HashMap<String, String>(); resolvedIncludePaths = new HashMap<String, List<IPath>>(); } public IDebugHandler getDebugHandler() { return debugHandler; } public DebugConnection getConnection() { return connection; } public void closeConnection() { connection.disconnect(); } public void connectionEstablished() { debugHandler.connectionEstablished(); } public void connectionClosed() { debugHandler.connectionClosed(); } public void closeDebugSession() { if (connection.isConnected()) { connection.sendNotification(new DebugSessionClosedNotification()); } } public void handleMultipleBindings() { debugHandler.multipleBindOccured(); } public void handlePeerResponseTimeout() { debugHandler.connectionTimedout(); } public boolean canDo(int feature) { switch (feature) { case START_PROCESS_FILE_NOTIFICATION: return getCurrentProtocolID() >= PROTOCOL_ID_2006040703; case GET_CWD: case GET_CALL_STACK_LITE: return getCurrentProtocolID() >= PROTOCOL_ID_2006040705; } return false; } /** * Asks Debug server for a current working directory (old way) * * @return current working directory, or <code>null</code> in case of error */ public String getCWDOld() { EvalRequest request = new EvalRequest(); request.setCommand("getcwd()"); //$NON-NLS-1$ IDebugResponseMessage response = sendCustomRequest(request); if (response != null && response instanceof EvalResponse) { String result = ((EvalResponse) response).getResult(); if (!EVAL_ERROR.equals(result)) { return result; } } return null; } /** * Asks Debug server for a current working directory (new way) * * @return current working directory, or <code>null</code> in case of error */ public String getCWDNew() { GetCWDRequest request = new GetCWDRequest(); IDebugResponseMessage response = sendCustomRequest(request); if (response != null && response.getStatus() == 0) { return ((GetCWDResponse) response).getCWD(); } return null; } public String getCWD() { if (!this.isActive()) { return null; } PHPDebugTarget debugTarget = getDebugHandler().getDebugTarget(); int suspendCount = debugTarget.getSuspendCount(); if (suspendCount == previousSuspendCount && cachedCWD != null) { return cachedCWD; } if (canDo(GET_CWD)) { cachedCWD = getCWDNew(); } else { cachedCWD = getCWDOld(); } return cachedCWD; } /** * Requesting file content from the Server * * @param fileName * @return byte array of the file content */ public byte[] getFileContent(String fileName) { try { FileContentRequest request = new FileContentRequest(); request.setFileName(fileName); IDebugResponseMessage response = sendCustomRequest(request); byte[] content = null; if (response != null && response instanceof FileContentResponse) { content = ((FileContentResponse) response).getContent(); } return content; } catch (Exception e) { Logger.logException(e); } return null; } /** * Returns local path corresponding to the current working directory of the * PHP script, which is currently running. * * @return current working directory */ public String getCurrentWorkingDirectory() { PHPDebugTarget debugTarget = debugHandler.getDebugTarget(); String cwd = getCWD(); if (cwd != null) { PathMapper pathMapper = PathMapperRegistry .getByLaunchConfiguration(debugTarget.getLaunch().getLaunchConfiguration()); if (pathMapper != null) { PathEntry cwdEntry = pathMapper.getLocalFile(cwd); if (cwdEntry != null) { cwd = cwdEntry.getResolvedPath(); } } } return cwd; } /** * Sets current working directory on the debugger side * * @param cwd * Current working directory to set * @return <code>true</code> if success, <code>false</code> - otherwise */ public boolean setCurrentWorkingDirectory(String cwd) { try { EvalRequest request = new EvalRequest(); request.setCommand(String.format("chdir('%1$s')", cwd)); //$NON-NLS-1$ IDebugResponseMessage response = sendCustomRequest(request); if (response != null && response instanceof EvalResponse) { String result = ((EvalResponse) response).getResult(); if (!EVAL_ERROR.equals(result)) { return true; } } } catch (Exception e) { Logger.logException(e); } return false; } /** * Returns local file name corresponding to the given remote path. This * method asks debugger for the current working directory before resolving. * * @param remoteFile * File to resolve * @return local file, or <code>null</code> in case of resolving failure */ public String convertToLocalFilename(String remoteFile) { if (isUseServerFiles()) { return remoteFile; } String currentScript = null; PHPstack callStack = getCallStack(); if (callStack == null) return null; if (callStack.getSize() > 0) { currentScript = callStack.getLayer(callStack.getSize() - 1).getResolvedCalledFileName(); } return convertToLocalFilename(remoteFile, getCurrentWorkingDirectory(), currentScript); } /** * Returns local file name corresponding to the given remote path * * @param remoteFile * File to resolve * @param cwd * Current working directory received from the debugger * @param currentScript * Script that is on the top of the debug stack currently * @return local file, or <code>null</code> in case of resolving failure */ public String convertToLocalFilename(String remoteFile, String cwd, String currentScript) { if (isUseServerFiles()) { return remoteFile; } PHPDebugTarget debugTarget = debugHandler.getDebugTarget(); if (debugTarget.getContextManager().isResolveBlacklisted(remoteFile)) { return remoteFile; } IWorkspace workspace = ResourcesPlugin.getWorkspace(); // check if this file is already local if (workspace.getRoot().findMember(remoteFile) != null) { return remoteFile; } // If we are running local debugger, check if "remote" file exists and // return it if it does if (debugTarget.isPHPCGI() && new File(remoteFile).exists()) { IFile wsFile = null; IPath location = new Path(remoteFile); IProject[] projects = workspace.getRoot().getProjects(); IProject currentProject = debugTarget.getProject(); // set current project to higher priority: for (int i = 0; i < projects.length; i++) { IProject project = projects[i]; if (project.equals(currentProject)) { IProject tmp = projects[0]; projects[0] = project; projects[i] = tmp; break; } } for (int i = 0; i < projects.length; i++) { IProject project = projects[i]; if (!project.isOpen() || !project.isAccessible()) { continue; } IPath projectLocation = project.getLocation(); if (projectLocation != null && projectLocation.isPrefixOf(location) && !projectLocation.equals(location)) { try { wsFile = workspace.getRoot() .getFile(project.getFullPath().append(location.makeRelativeTo(projectLocation))); } catch (Exception ex) { Logger.logException(ex); } break; } } if (wsFile != null) { return wsFile.getFullPath().toString(); } else { return remoteFile; } } String resolvedFileKey = new StringBuilder(remoteFile).append(cwd).append(currentScript).toString(); if (!resolvedFiles.containsKey(resolvedFileKey)) { String currentScriptDir = null; if (currentScript != null) { currentScriptDir = new Path(currentScript).removeLastSegments(1).toString(); } String resolvedFile = null; PathEntry pathEntry = DebugSearchEngine.find(remoteFile, debugTarget, cwd, currentScriptDir); if (pathEntry != null) { resolvedFile = pathEntry.getResolvedPath(); } else { try { resolvedFile = tryGuessMapping(remoteFile, debugTarget); } catch (ModelException e) { resolvedFile = remoteFile; } } resolvedFiles.put(resolvedFileKey, resolvedFile); } String localFile = resolvedFiles.get(resolvedFileKey); if (localFile == null) { return remoteFile; } return localFile; } /** * Returns remote file name corresponding to the given local path * * @param localFile * @return remote file path, or localFile in case it couldn't be resolved */ public static String convertToRemoteFilename(String localFile, PHPDebugTarget debugTarget) { IPath path = Path.fromPortableString(localFile); /* * check if this is valid workspace path to avoid IAE e.g. when using * debug URL, localFile can be "c:\Program Files\..." see * https://bugs.eclipse.org/bugs/show_bug.cgi?id=306834 */ if (path.segmentCount() >= 2) { if (debugTarget.isPHPCGI()) { IFile workspaceFile = ResourcesPlugin.getWorkspace().getRoot().getFile(path); try { String localFileLocation = localFile; IPath wsFileLocation = workspaceFile.getLocation(); if (wsFileLocation != null) { localFileLocation = wsFileLocation.makeAbsolute().toOSString(); } return FileUtils.toRealPath(localFileLocation, true); } catch (IOException e) { // ignore as target file might just not exist } } } if (VirtualPath.isAbsolute(localFile)) { PathMapper pathMapper = PathMapperRegistry .getByLaunchConfiguration(debugTarget.getLaunch().getLaunchConfiguration()); if (pathMapper != null) { String remoteFile = pathMapper.getRemoteFile(localFile); if (remoteFile != null) { return remoteFile; } } } return localFile; } // --------------------------------------------------------------------------- /** * Sends the request through the communication connection and returns * response * * @param message * request that will be sent to the debugger * @return message response recieved from the debugger */ public IDebugResponseMessage sendCustomRequest(IDebugRequestMessage request) { IDebugResponseMessage response = null; if (this.isActive()) { try { Object obj = connection.sendRequest(request); if (obj instanceof IDebugResponseMessage) { response = (IDebugResponseMessage) obj; } } catch (Exception e) { e.printStackTrace(); } } return response; } /** * Sends custom notification through the communication connection * * @param message * notification that will be delivered to the debugger * @return <code>true</code> if succeeded sending the message, * <code>false</code> - otherwise */ public boolean sendCustomNotification(IDebugNotificationMessage notification) { if (this.isActive()) { try { connection.sendNotification(notification); return true; } catch (Exception e) { e.printStackTrace(); } } return false; } /** * Asynchronic addBreakpoint Returns true if succeeded sending the request, * false otherwise. */ public boolean addBreakpoint(Breakpoint bp, BreakpointAddedResponseHandler responseHandler) { if (!this.isActive()) { return false; } try { AddBreakpointRequest request = new AddBreakpointRequest(); Breakpoint tmpBreakpoint = (Breakpoint) bp.clone(); String fileName = tmpBreakpoint.getFileName(); tmpBreakpoint.setFileName(fileName); request.setBreakpoint(tmpBreakpoint); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * Synchronic addBreakpoint Returns true if succeeded adding the Breakpoint. */ public void addBreakpoint(Breakpoint breakpoint) { if (!this.isActive()) { return; } try { AddBreakpointRequest request = new AddBreakpointRequest(); Breakpoint tmpBreakpoint = (Breakpoint) breakpoint.clone(); String fileName = tmpBreakpoint.getFileName(); tmpBreakpoint.setFileName(fileName); request.setBreakpoint(tmpBreakpoint); AddBreakpointResponse response = (AddBreakpointResponse) connection.sendRequest(request); if (response != null && response.getStatus() == 0) { // Log.writeLog("addBreakpoint"); breakpoint.setID(response.getBreakpointID()); } } catch (Exception exc) { exc.printStackTrace(); } } /** * Asynchronic removeBreakpoint Returns true if succeeded sending the * request, false otherwise. */ public boolean removeBreakpoint(int id, BreakpointRemovedResponseHandler responseHandler) { if (!this.isActive()) { return false; } CancelBreakpointRequest request = new CancelBreakpointRequest(); request.setBreakpointID(id); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } /** * Synchronic removeBreakpoint Returns true if succeeded removing the * Breakpoint. */ public boolean removeBreakpoint(int id) { if (!this.isActive()) { return false; } try { CancelBreakpointRequest request = new CancelBreakpointRequest(); request.setBreakpointID(id); CancelBreakpointResponse response = (CancelBreakpointResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic removeBreakpoint Returns true if succeeded sending the * request, false otherwise. */ public boolean removeBreakpoint(Breakpoint breakpoint, BreakpointRemovedResponseHandler responseHandler) { return removeBreakpoint(breakpoint.getID(), responseHandler); } /** * Synchronic removeBreakpoint Returns true if succeeded removing the * Breakpoint. */ public boolean removeBreakpoint(Breakpoint breakpoint) { return removeBreakpoint(breakpoint.getID()); } /** * Asynchronic removeAllBreakpoints Returns true if succeeded sending the * request, false otherwise. */ public boolean removeAllBreakpoints(AllBreakpointRemovedResponseHandler responseHandler) { if (!this.isActive()) { return false; } CancelAllBreakpointsRequest request = new CancelAllBreakpointsRequest(); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } /** * Synchronic removeAllBreakpoints Returns true if succeeded removing all * the Breakpoint. */ public boolean removeAllBreakpoints() { if (!this.isActive()) { return false; } try { CancelAllBreakpointsRequest request = new CancelAllBreakpointsRequest(); CancelAllBreakpointsResponse response = (CancelAllBreakpointsResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic stepInto Returns true if succeeded sending the request, false * otherwise. */ public boolean stepInto(StepIntoResponseHandler responseHandler) { if (!this.isActive()) { return false; } StepIntoRequest request = new StepIntoRequest(); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } /** * Synchronic stepInto Returns true if succeeded stepInto. */ public boolean stepInto() { if (!this.isActive()) { return false; } try { StepIntoRequest request = new StepIntoRequest(); StepIntoResponse response = (StepIntoResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic stepOver Returns true if succeeded sending the request, false * otherwise. */ public boolean stepOver(StepOverResponseHandler responseHandler) { if (!this.isActive()) { return false; } try { StepOverRequest request = new StepOverRequest(); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic stepOver Returns true if succeeded stepOver. */ public boolean stepOver() { if (!this.isActive()) { return false; } try { StepOverRequest request = new StepOverRequest(); StepOverResponse response = (StepOverResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic stepOut Returns true if succeeded sending the request, false * otherwise. */ public boolean stepOut(StepOutResponseHandler responseHandler) { if (!this.isActive()) { return false; } try { StepOutRequest request = new StepOutRequest(); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic stepOut Returns true if succeeded stepOut. */ public boolean stepOut() { if (!this.isActive()) { return false; } try { StepOutRequest request = new StepOutRequest(); StepOutResponse response = (StepOutResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic go Returns true if succeeded sending the request, false * otherwise. */ public boolean go(GoResponseHandler responseHandler) { if (!this.isActive()) { return false; } try { GoRequest request = new GoRequest(); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic go Returns true if succeeded go. */ public boolean go() { if (!this.isActive()) { return false; } try { GoRequest request = new GoRequest(); GoResponse response = (GoResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic start Returns true if succeeded sending the request, false * otherwise. */ public boolean start(StartResponseHandler responseHandler) { if (!this.isActive()) { return false; } if (!detectProtocolID()) { return false; } try { debugHandler.getDebugTarget().installDeferredBreakpoints(); } catch (CoreException ce) { return false; } StartRequest request = new StartRequest(); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic start Returns true if succeeded start. */ public boolean start() { if (!this.isActive()) { return false; } if (!detectProtocolID()) { return false; } StartRequest request = new StartRequest(); try { StartResponse response = (StartResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronous addFiles Returns true if succeeded sending the request, * false otherwise. */ public boolean addFiles(String[] paths, AddFilesResponseHandler responseHandler) { if (!this.isActive()) { return false; } try { AddFilesRequest request = new AddFilesRequest(); request.setPaths(paths); connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * Synchronous addFiles Returns true if succeeded adding the Breakpoint. */ public boolean addFiles(String[] paths) { if (!this.isActive()) { return false; } try { AddFilesRequest request = new AddFilesRequest(); request.setPaths(paths); AddFilesResponse response = (AddFilesResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * This method is used for detecting protocol version of Debugger * * @return <code>true</code> if succeeded to detect, otherwise * <code>false</code> */ protected boolean detectProtocolID() { String isWebServerLaunch = getDebugHandler().getDebugTarget().getLaunch() .getAttribute(IDebugParametersKeys.WEB_SERVER_DEBUGGER); boolean isWebDebug = (isWebServerLaunch != null && Boolean.valueOf(isWebServerLaunch).booleanValue()); boolean isUseNewProtocol = true; try { ILaunchConfiguration config = getDebugHandler().getDebugTarget().getLaunch().getLaunchConfiguration(); String debuggerId = config.getAttribute(PHPDebugCorePreferenceNames.PHP_DEBUGGER_ID, DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID); if (DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID.equals(debuggerId)) { ZendDebuggerConfiguration debuggerConfiguration = (ZendDebuggerConfiguration) PHPDebuggersRegistry .getDebuggerConfiguration(debuggerId); isUseNewProtocol = debuggerConfiguration.isUseNewProtocol(); } } catch (CoreException e) { Logger.logException(e); } if (isWebDebug && PHPDebugUtil.isSystem5()) { if (setProtocol(COMMERCIAL_I5_PROTOCOL_ID_LATEST)) { return true; } if (setProtocol(COMMERCIAL_PROTOCOL_ID_2006040905)) { warnOlderDebugVersion(); return true; } if (setProtocol(COMMERCIAL_I5_PROTOCOL_ID_2006040904)) { warnOlderDebugVersion(); return true; } if (setProtocol(COMMERCIAL_I5_PROTOCOL_ID_2006040902)) { warnOlderDebugVersion(); return true; } // Debugger is not an i5 edition warnNonI5Debugger(); finish(); } else { // check whether debugger is using the latest protocol ID: if (isUseNewProtocol && setProtocol(COMMERCIAL_PROTOCOL_ID_LATEST)) { return true; } if (setProtocol(COMMERCIAL_PROTOCOL_ID_2006040905)) { // do not warn that it is old, it does not support Add Files // only return true; } if (setProtocol(COMMERCIAL_PROTOCOL_ID_2006040903)) { warnOlderDebugVersion(); return true; } // check whether debugger is using one of older protocol ID: if (setProtocol(COMMERCIAL_PROTOCOL_ID_2006040901)) { // warn user that he is using an old debugger warnOlderDebugVersion(); return true; } // All of above failed, check if the connection is active (it could // be // terminated in the meantime by the user i.e.) if (!isActive()) return true; // user is using an incompatible version of debugger: getDebugHandler().wrongDebugServer(); } return false; } /** * Detects PHP version that debug session is running on. */ protected void detectPHPVersion() { phpVersion = eval("phpversion()"); //$NON-NLS-1$ } public static void warnOlderDebugVersion() { boolean dontShowWarning = Platform.getPreferencesService().getBoolean(PHPDebugPlugin.ID, "DontShowOlderDebuggerWarning", false, //$NON-NLS-1$ null); if (!dontShowWarning) { Display.getDefault().asyncExec(new Runnable() { public void run() { OldDebuggerWarningDialog dialog = new OldDebuggerWarningDialog( Display.getDefault().getActiveShell()); dialog.open(); } }); } } public static void warnNonI5Debugger() { Display.getDefault().asyncExec(new Runnable() { public void run() { MessageDialog.openError(Display.getDefault().getActiveShell(), PHPDebugCoreMessages.RemoteDebugger_LicenseError, PHPDebugCoreMessages.RemoteDebugger_WarnNoneI5); } }); } public static void requestRemoteFile(final IRemoteFileContentRequestor requester, String fileName, final int lineNumber, String remoteURL) { RemoteFileContentRequestorsRegistry.getInstance().addRequestor(requester, fileName, lineNumber); final StringBuilder urlBuf = new StringBuilder(RemoteFileQuery.generateQuery(remoteURL)); try { fileName = URLEncoder.encode(fileName, "UTF-8"); //$NON-NLS-1$ } catch (UnsupportedEncodingException e) { } urlBuf.append("&") //$NON-NLS-1$ .append(DefaultDebugParametersInitializer.GET_FILE_CONTENT).append('=').append(fileName); urlBuf.append("&") //$NON-NLS-1$ .append(DefaultDebugParametersInitializer.LINE_NUMBER).append('=').append(lineNumber); final String fileName2 = fileName; // Send the request to the server: Job requestFileJob = new Job(PHPDebugCoreMessages.RemoteDebugger_RequestFileFromServer) { public IStatus run(IProgressMonitor monitor) { try { URL requestURL = new URL(urlBuf.toString()); URLConnection connection = requestURL.openConnection(); InputStream inputStream = connection.getInputStream(); while (inputStream.read() != -1) { // do nothing on the content returned by standard stream } requester.requestCompleted(null); } catch (Exception e) { RemoteFileContentRequestorsRegistry.getInstance().removeRequestor(fileName2, lineNumber); requester.requestCompleted(e); Logger.logException(e); } finally { monitor.done(); } return Status.OK_STATUS; } }; requestFileJob.setUser(false); requestFileJob.schedule(); } public boolean setProtocol(int protocolID) { SetProtocolRequest request = new SetProtocolRequest(); request.setProtocolID(protocolID); IDebugResponseMessage response = sendCustomRequest(request); if (response != null && response instanceof SetProtocolResponse) { int responceProtocolID = ((SetProtocolResponse) response).getProtocolID(); if (responceProtocolID == protocolID) { currentProtocolId = protocolID; return true; } } return false; } public int getCurrentProtocolID() { return currentProtocolId; } /** * Asynchronic pause Returns true if succeeded sending the request, false * otherwise. */ public boolean pause(PauseResponseHandler responseHandler) { if (!this.isActive()) { return false; } PauseDebuggerRequest request = new PauseDebuggerRequest(); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic pause Returns true if succeeded pause. */ public boolean pause() { if (!this.isActive()) { return false; } PauseDebuggerRequest request = new PauseDebuggerRequest(); try { PauseDebuggerResponse response = (PauseDebuggerResponse) connection.sendRequest(request); return response != null && response.getStatus() == 0; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Asynchronic pause Returns true if succeeded sending the request, false * otherwise. */ public boolean eval(String commandString, EvalResponseHandler responseHandler) { if (!this.isActive()) { return false; } EvalRequest request = new EvalRequest(); request.setCommand(commandString); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } public boolean assignValue(String var, String value, int depth, String[] path, AssignValueResponseHandler responseHandler) { if (!this.isActive()) { return false; } AssignValueRequest request = new AssignValueRequest(); request.setVar(var); request.setValue(value); request.setDepth(depth); request.setPath(path); request.setTransferEncoding(getTransferEncoding()); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /* * Returns the transfer encoding for the current project. */ private String getTransferEncoding() { IProject project = debugHandler.getDebugTarget().getProject(); return project == null ? null : PHPProjectPreferences.getTransferEncoding(project); } /** * aSynchronic assigned value */ public boolean assignValue(String var, String value, int depth, String[] path) { if (!this.isActive()) { return false; } AssignValueRequest request = new AssignValueRequest(); request.setVar(var); request.setValue(value); request.setDepth(depth); request.setPath(path); request.setTransferEncoding(getTransferEncoding()); try { connection.sendRequest(request); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic pause Returns true if succeeded pause. */ public String eval(String commandString) { if (!this.isActive()) { return null; } EvalRequest request = new EvalRequest(); request.setCommand(commandString); try { EvalResponse response = (EvalResponse) connection.sendRequest(request); String result = null; if (response != null) { if (response.getStatus() == 0) { result = response.getResult(); } else { result = "---ERROR---"; //$NON-NLS-1$ } } return result; } catch (Exception exc) { exc.printStackTrace(); } return null; } /** * Finish the debugger running. */ public void finish() { connection.disconnect(); } /** * Checks if there is a connection. */ public boolean isActive() { return connection != null && connection.isConnected(); } public boolean getVariableValue(String var, int depth, String[] path, VariableValueResponseHandler responseHandler) { if (!this.isActive()) { return false; } GetVariableValueRequest request = new GetVariableValueRequest(); request.setVar(var); request.setDepth(depth); request.setPath(path); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic getVariableValue Returns the variable var. */ public byte[] getVariableValue(String var, int depth, String[] path) throws IllegalArgumentException { if (!this.isActive()) { return null; } GetVariableValueRequest request = new GetVariableValueRequest(); request.setVar(var); request.setDepth(depth); request.setPath(path); GetVariableValueResponse response = null; try { response = (GetVariableValueResponse) connection.sendRequest(request); } catch (Exception exc) { exc.printStackTrace(); } if (response == null || response.getStatus() != 0) { return null; } return response.getVarResult(); } public boolean getCallStack(GetCallStackResponseHandler responseHandler) { if (!this.isActive()) { return false; } GetCallStackRequest request = new GetCallStackRequest(); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic getCallStack Returns the Stack layer with no variables * information. */ public PHPstack getCallStack() { if (!this.isActive()) { return null; } PHPDebugTarget debugTarget = getDebugHandler().getDebugTarget(); int suspendCount = debugTarget.getSuspendCount(); if (suspendCount == previousSuspendCount && cachedStack != null) { return cachedStack; } GetCallStackRequest request = new GetCallStackRequest(); PHPstack remoteStack = null; try { GetCallStackResponse response = (GetCallStackResponse) connection.sendRequest(request); if (response != null) { remoteStack = response.getPHPstack(); } } catch (Exception exc) { exc.printStackTrace(); } convertToSystem(remoteStack); previousSuspendCount = suspendCount; cachedStack = remoteStack; return remoteStack; } private void convertToSystem(PHPstack remoteStack) { if (remoteStack != null) { String currentWorkingDir = getCurrentWorkingDirectory(); for (int i = 0; i < remoteStack.getSize(); i++) { StackLayer layer = remoteStack.getLayer(i); layer.setCallerLineNumber(layer.getCallerLineNumber() - 1); layer.setCalledLineNumber(layer.getCalledLineNumber() - 1); layer.setResolvedCalledFileName(layer.getCalledFileName()); if (i > 0) { String previousScript = remoteStack.getLayer(i - 1).getResolvedCalledFileName(); String previousScriptDir = "."; //$NON-NLS-1$ int idx = Math.max(previousScript.lastIndexOf('/'), previousScript.lastIndexOf('\\')); if (idx != -1) { previousScriptDir = previousScript.substring(0, idx); } IProject project = null; IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(previousScript); if (resource != null) { project = resource.getProject(); } else { project = debugHandler.getDebugTarget().getProject(); } if (layer.getCalledFileName() != null && currentWorkingDir != null && project != null) { Result<?, ?> result = PHPSearchEngine.find(layer.getCalledFileName(), currentWorkingDir, previousScriptDir, project); if (result instanceof ResourceResult) { layer.setResolvedCalledFileName( ((ResourceResult) result).getFile().getFullPath().toString()); } else if (result instanceof IncludedFileResult) { layer.setResolvedCalledFileName(((IncludedFileResult) result).getFile().getAbsolutePath()); } else if (result instanceof ExternalFileResult) { layer.setResolvedCalledFileName(((ExternalFileResult) result).getFile().getAbsolutePath()); } } } } } } public boolean getStackVariableValue(int stackDepth, String value, int depth, String[] path, GetStackVariableValueResponseHandler responseHandler) { if (!this.isActive()) { return false; } GetStackVariableValueRequest request = new GetStackVariableValueRequest(); request.setVar(value); request.setDepth(depth); request.setLayerDepth(stackDepth); request.setPath(path); try { connection.sendRequest(request, new ThisHandleResponse(responseHandler)); return true; } catch (Exception exc) { exc.printStackTrace(); } return false; } /** * Synchronic getStackVariableValue Returns the variable value. */ public byte[] getStackVariableValue(int stackDepth, String value, int depth, String[] path) { if (!this.isActive()) { return null; } GetStackVariableValueRequest request = new GetStackVariableValueRequest(); request.setVar(value); request.setDepth(depth); request.setLayerDepth(stackDepth); request.setPath(path); GetStackVariableValueResponse response = null; try { response = (GetStackVariableValueResponse) connection.sendRequest(request); } catch (Exception exc) { exc.printStackTrace(); } if (response == null || response.getStatus() != 0) { return null; } return response.getVarResult(); } private String tryGuessMapping(String remoteFile, PHPDebugTarget debugTarget) throws ModelException { IProject project = debugTarget.getProject(); if (project == null) { String orginalURL = debugTarget.getLaunch().getAttribute(IDebugParametersKeys.ORIGINAL_URL); if (orginalURL != null) { String projectName = new Path(orginalURL).segment(0); IWorkspace workspace = ResourcesPlugin.getWorkspace(); project = workspace.getRoot().getProject(projectName); } } if (project != null) { String projectName = project.getName(); IPath remotePath = new Path(remoteFile); int size = remotePath.segmentCount(); for (int j = 0; j < size; j++) { String segment = remotePath.segment(j); if (segment.equals(projectName)) { remotePath = remotePath.removeFirstSegments(j); size = remotePath.segmentCount(); for (int i = 0; i < size; i++) { remotePath = remotePath.removeFirstSegments(1); if (remotePath.segmentCount() > 0) { IResource res = project.getFile(remotePath); if (res != null && res.exists()) { return project.getFullPath().append(remotePath).toString(); } } } List<IPath> includePaths = getIncludePaths(project); if (includePaths.size() > 0) { return checkIncludePaths(remoteFile, includePaths); } break; } } } return remoteFile; } private String checkIncludePaths(String remoteFile, List<IPath> includePaths) { IPath remotePath = new Path(remoteFile); int size = remotePath.segmentCount(); for (int i = 0; i < size; i++) { if (remotePath.segmentCount() > 0) { for (IPath includePath : includePaths) { File file = includePath.append(remotePath).toFile(); if (file.exists()) { return file.toString(); } } remotePath = remotePath.removeFirstSegments(1); } } return remoteFile; } private boolean isUseServerFiles() { boolean useServerFiles = false; try { useServerFiles = getDebugHandler().getDebugTarget().getLaunch().getLaunchConfiguration() .getAttribute(IPHPDebugConstants.DEBUGGING_USE_SERVER_FILES, false); } catch (CoreException e) { PHPDebugPlugin.log(e); } return useServerFiles; } public List<IPath> getIncludePaths(IProject project) throws ModelException { if (project == null) return new ArrayList<IPath>(); List<IPath> includePaths = resolvedIncludePaths.get(project.getName()); if (includePaths != null) { return includePaths; } else { includePaths = new ArrayList<IPath>(); } IncludePath[] paths = IncludePathManager.getInstance().getIncludePaths(project); for (IncludePath includePath : paths) { if (includePath.getEntry() instanceof IBuildpathEntry) { IBuildpathEntry bPath = (IBuildpathEntry) includePath.getEntry(); if (bPath.getEntryKind() == IBuildpathEntry.BPE_CONTAINER && !bPath.getPath().toString().equals("org.eclipse.php.core.LANGUAGE")) { //$NON-NLS-1$ IBuildpathContainer buildpathContainer = DLTKCore.getBuildpathContainer(bPath.getPath(), DLTKCore.create(project)); if (buildpathContainer != null) { final IBuildpathEntry[] buildpathEntries = buildpathContainer.getBuildpathEntries(); for (IBuildpathEntry buildpathEntry : buildpathEntries) { IPath localPath = EnvironmentPathUtils.getLocalPath(buildpathEntry.getPath()); includePaths.add(localPath); } } } } } resolvedIncludePaths.put(project.getName(), includePaths); return includePaths; } /** * Requests Code Coverage information from the debugger and returns it. * * @return CodeCoverageData[] Code coverage information. If this remote * debugger is not active, or in case of error this method returns * <code>null</code>. */ public CodeCoverageData[] getCodeCoverageData() { if (!this.isActive()) { return null; } GetCodeCoverageRequest request = new GetCodeCoverageRequest(); IDebugResponseMessage response = sendCustomRequest(request); if (response != null && response instanceof GetCodeCoverageResponse) { CodeCoverageData[] codeCoverageData = ((GetCodeCoverageResponse) response).getCodeCoverageData(); for (int i = 0; i < codeCoverageData.length; ++i) { String localFileName = convertToLocalFilename(codeCoverageData[i].getFileName(), null, null); if (localFileName == null) { localFileName = codeCoverageData[i].getFileName(); } codeCoverageData[i].setLocalFileName(localFileName); codeCoverageData[i].setURL(getDebugHandler().getDebugTarget().getURL()); } return codeCoverageData; } return null; } @Override public String getPHPVersion() { if (phpVersion == null) { detectPHPVersion(); } return phpVersion; } // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- private class ThisHandleResponse implements ResponseHandler { Object responseHandler; public ThisHandleResponse(Object responseHandler) { this.responseHandler = responseHandler; } public void handleResponse(Object request, Object response) { boolean success = response != null && ((IDebugResponseMessage) response).getStatus() == 0; if (request instanceof AddBreakpointRequest) { AddBreakpointRequest addBreakpointRequest = (AddBreakpointRequest) request; Breakpoint bp = addBreakpointRequest.getBreakpoint(); String fileName = bp.getFileName(); int lineNumber = bp.getLineNumber(); int id = -1; if (response != null) { id = ((AddBreakpointResponse) response).getBreakpointID(); } ((BreakpointAddedResponseHandler) responseHandler).breakpointAdded(fileName, lineNumber, id, success); } else if (request instanceof CancelBreakpointRequest) { ((BreakpointRemovedResponseHandler) responseHandler) .breakpointRemoved(((CancelBreakpointRequest) request).getBreakpointID(), success); } else if (request instanceof CancelAllBreakpointsRequest) { ((AllBreakpointRemovedResponseHandler) responseHandler).allBreakpointRemoved(success); } else if (request instanceof StartRequest) { ((StartResponseHandler) responseHandler).started(success); } else if (request instanceof EvalRequest) { if (response != null) { ((EvalResponseHandler) responseHandler).evaled(((EvalRequest) request).getCommand(), success ? ((EvalResponse) response).getResult() : null, success); } } else if (request instanceof StepIntoRequest) { ((StepIntoResponseHandler) responseHandler).stepInto(success); } else if (request instanceof StepOverRequest) { ((StepOverResponseHandler) responseHandler).stepOver(success); } else if (request instanceof StepOutRequest) { ((StepOutResponseHandler) responseHandler).stepOut(success); } else if (request instanceof GoRequest) { ((GoResponseHandler) responseHandler).go(success); } else if (request instanceof PauseDebuggerRequest) { ((PauseResponseHandler) responseHandler).pause(success); } else if (request instanceof AssignValueRequest) { AssignValueRequest assignValueRequest = (AssignValueRequest) request; String var = assignValueRequest.getVar(); String value = assignValueRequest.getValue(); int depth = assignValueRequest.getDepth(); String[] path = assignValueRequest.getPath(); ((AssignValueResponseHandler) responseHandler).valueAssigned(var, value, depth, path, success); } else if (request instanceof GetVariableValueRequest) { GetVariableValueRequest getVariableValueRequest = (GetVariableValueRequest) request; String value = getVariableValueRequest.getVar(); int depth = getVariableValueRequest.getDepth(); String[] path = getVariableValueRequest.getPath(); String result = null; if (response != null) { try { result = new String(((GetVariableValueResponse) response).getVarResult(), ((IDebugMessage) response).getTransferEncoding()); } catch (UnsupportedEncodingException e) { } } ((VariableValueResponseHandler) responseHandler).variableValue(value, depth, path, result, success); } else if (request instanceof GetCallStackRequest) { PHPstack remoteStack = null; if (response != null) { remoteStack = ((GetCallStackResponse) response).getPHPstack(); } convertToSystem(remoteStack); ((GetCallStackResponseHandler) responseHandler).callStack(remoteStack, success); } else if (request instanceof GetStackVariableValueRequest) { GetStackVariableValueRequest getStackVariableValueRequest = (GetStackVariableValueRequest) request; int stackDepth = getStackVariableValueRequest.getLayerDepth(); String value = getStackVariableValueRequest.getVar(); int depth = getStackVariableValueRequest.getDepth(); String[] path = getStackVariableValueRequest.getPath(); String result = null; if (response != null) { try { result = new String(((GetStackVariableValueResponse) response).getVarResult(), ((IDebugMessage) response).getTransferEncoding()); } catch (UnsupportedEncodingException e) { } } ((GetStackVariableValueResponseHandler) responseHandler).stackVariableValue(stackDepth, value, depth, path, result, success); } } } }