/*******************************************************************************
* Copyright (c) 2015 Zend Technologies 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:
* Zend Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.php.internal.debug.core.xdebug.dbgp;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.php.internal.debug.core.PHPDebugCoreMessages;
import org.eclipse.php.internal.debug.core.debugger.DebuggerSettingsManager;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerSettings;
import org.eclipse.php.internal.debug.core.debugger.IDebuggerSettingsListener;
import org.eclipse.php.internal.debug.core.xdebug.XDebugPreferenceMgr;
import org.eclipse.php.internal.debug.core.xdebug.XDebugPreferenceMgr.AcceptRemoteSession;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.protocol.DBGpResponse;
import org.eclipse.swt.widgets.Display;
/**
* This class handles the management of DBGp proxy interaction.
*/
public class DBGpProxyHandler {
private class SettingsListener implements IDebuggerSettingsListener {
@Override
public void settingsAdded(IDebuggerSettings settings) {
// ignore
}
@Override
public void settingsRemoved(IDebuggerSettings settings) {
// ignore
}
@Override
public void settingsChanged(PropertyChangeEvent[] events) {
boolean configure = false;
for (PropertyChangeEvent event : events) {
IDebuggerSettings source = (IDebuggerSettings) event.getSource();
if (!source.getOwnerId().equals(getOwnerId()))
continue;
configure = true;
}
if (configure)
configure();
}
}
private boolean registered = false;
private String currentIdeKey = null;
private String proxyHost = ""; //$NON-NLS-1$
private int proxyPort = DEFAULT_PROXY_PORT;
private int idePort = -1;
private int errorCode = 0;
private String errorMsg = ""; //$NON-NLS-1$
private boolean multisession = false;
private String ownerId;
private IDebuggerSettingsListener ownerSettingsListener = new SettingsListener();
private static final int DEFAULT_PROXY_PORT = 9001;
private static final int PROXY_CONNECT_TIMEOUT = 3000; // 3 seconds
public DBGpProxyHandler(String ownerId) {
this.ownerId = ownerId;
DebuggerSettingsManager.INSTANCE.addSettingsListener(ownerSettingsListener);
}
public String getOwnerId() {
return ownerId;
}
/**
* Register with the proxy if we are not already registered.
*
* @return true if registration successful
*/
public boolean registerWithProxy() {
/*
* proxyinit -a ip:port -k ide_key -m [0|1]
*
* -p the port that the IDE listens for debugging on. The address is
* retrieved from the connection information. -k a IDE key, which the
* debugger engine will also use in it's debugging init command. this
* allows the proxy to match request to IDE. Typically the user will
* provide the session key as a configuration item. -m this tells the
* demon that the IDE supports (or doesn't) multiple debugger sessions.
* if -m is missing, zero or no support is default.
*
* response <proxyinit success="[0|1]" idekey="{ID}"
* address="{IP_ADDRESS}" port="{NUM}> <error
* id="app_specific_error_code"> <message>UI Usable Message</message>
* </error> </proxyinit>
*/
if (!registered) {
DBGpResponse resp = sendcmd(
"proxyinit -p " + idePort + " -k " + currentIdeKey + " -m " + (multisession ? "1" : "0")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
if (resp != null) {
if (resp.getType() == DBGpResponse.PROXY_INIT && resp.getErrorCode() == DBGpResponse.ERROR_OK) {
registered = true;
} else {
errorCode = resp.getErrorCode();
errorMsg = resp.getErrorMessage();
}
}
}
return registered;
}
/**
* unregister from the proxy.
*/
public void unregister() {
// IDE command
//
// proxystop -k ide_key
if (registered) {
DBGpResponse resp = sendcmd("proxystop" + " -k " + currentIdeKey); //$NON-NLS-1$ //$NON-NLS-2$
registered = false;
if (resp == null)
// Proxy could be shutdown externally
return;
String isOk = DBGpResponse.getAttribute(resp.getParentNode(), "success"); //$NON-NLS-1$
if (isOk == null || !isOk.equals("1")) { //$NON-NLS-1$
DBGpLogger.logWarning("Unexpected response from proxystop. ErrorCode=" + resp.getErrorCode() + ". msg=" //$NON-NLS-1$ //$NON-NLS-2$
+ resp.getErrorMessage(), this, null);
}
}
}
public void dispose() {
DebuggerSettingsManager.INSTANCE.removeSettingsListener(ownerSettingsListener);
}
/**
* return true if registered with a proxy.
*
* @return
*/
public boolean isRegistered() {
return registered;
}
/**
* get the last received error code.
*
* @return error code
*/
public int getErrorCode() {
return errorCode;
}
/**
* get the last received error message.
*
* @return error message
*/
public String getErrorMsg() {
return errorMsg;
}
/**
* configure the Proxy Handler from the preferences.
*/
public void configure() {
if (useProxy() == false) {
unregister();
} else {
int idePort = XDebugDebuggerSettingsUtil.getDebugPort(getOwnerId());
String ideKey = XDebugDebuggerSettingsUtil.getProxyIdeKey(getOwnerId());
boolean multisession = XDebugPreferenceMgr.useMultiSession();
String proxy = XDebugDebuggerSettingsUtil.getProxyAddress(getOwnerId());
String proxyHost = proxy;
int proxyPort = DEFAULT_PROXY_PORT;
int split = proxy.indexOf(':');
if (split != -1) {
proxyHost = proxy.substring(0, split);
if (split + 1 < proxy.length()) {
String portStr = proxy.substring(split + 1);
try {
proxyPort = Integer.parseInt(portStr);
} catch (NumberFormatException e) {
}
}
}
if (proxyPort == idePort) {
displayErrorMessage(PHPDebugCoreMessages.XDebug_DBGpProxyHandler_0);
} else {
setProxyInfo(proxyHost, proxyPort, ideKey, idePort, multisession);
if (XDebugPreferenceMgr.getAcceptRemoteSession() != AcceptRemoteSession.off) {
/*
* if JIT we must register with the proxy straight away
* rather than wait for the first launch
*/
Job job = new Job(PHPDebugCoreMessages.DBGpProxyConnection_Registering_DBGp_proxy) {
@Override
protected IStatus run(IProgressMonitor monitor) {
if (registerWithProxy() == false) {
displayErrorMessage(PHPDebugCoreMessages.XDebug_DBGpProxyHandler_1 + getErrorMsg());
XDebugPreferenceMgr.setUseProxy(false);
}
return Status.OK_STATUS;
}
};
job.schedule();
}
}
}
}
/**
* Store the proxy information.
*
* @param host
* @param port
* @param idekey
* @param listeningPort
* @param multisession
*/
private void setProxyInfo(String host, int port, String idekey, int listeningPort, boolean multisession) {
if (!host.equalsIgnoreCase(proxyHost) || port != proxyPort || !idekey.equals(currentIdeKey)
|| idePort != listeningPort || this.multisession != multisession) {
unregister(); // checks for connection
proxyHost = host;
proxyPort = port;
idePort = listeningPort;
currentIdeKey = idekey;
this.multisession = multisession;
}
}
/**
* Checks if proxy is enabled.
*
* @return <code>true</code> if proxy is enabled, <code>false</code>
* otherwise
*/
public boolean useProxy() {
return XDebugDebuggerSettingsUtil.getProxyEnabled(getOwnerId());
}
/**
* Send a command to the proxy and get a response.
*
* @param cmd
* the command to send
* @return the response.
*/
private DBGpResponse sendcmd(String cmd) {
DBGpResponse dbgpResp = null;
try {
// TODO: look at reducing the timeout for connection failure.
Socket s = new Socket();
InetSocketAddress server = new InetSocketAddress(proxyHost, proxyPort);
InetSocketAddress local = new InetSocketAddress(0);
s.bind(local);
s.connect(server, PROXY_CONNECT_TIMEOUT);
InputStream is = s.getInputStream();
OutputStream os = s.getOutputStream();
if (DBGpLogger.debugCmd()) {
DBGpLogger.debug("cmd: " + cmd); //$NON-NLS-1$
}
os.write(cmd.getBytes("ASCII")); // command will //$NON-NLS-1$
// always be ASCII
os.flush();
byte[] resp = readResponse(is);
dbgpResp = new DBGpResponse();
dbgpResp.parseResponse(resp);
s.shutdownInput();
s.shutdownOutput();
s.close();
return dbgpResp;
} catch (IOException ioe) {
if (dbgpResp == null) {
errorCode = 9999;
if (ioe instanceof EOFException) {
errorMsg = PHPDebugCoreMessages.XDebug_DBGpProxyHandler_2;
} else {
errorMsg = ioe.getMessage();
if (errorMsg == null) {
errorMsg = ioe.getClass().getName();
}
}
}
}
return dbgpResp;
}
/**
* Read from the socket input stream and create the response.
*
* @param is
* socket input stream
* @return a response
* @throws IOException
*/
private byte[] readResponse(InputStream is) throws IOException {
byte byteArray[];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while ((b = is.read()) != -1) {
baos.write(b);
}
byteArray = baos.toByteArray();
if (DBGpLogger.debugResp()) {
DBGpLogger.debug("Response: " + new String(byteArray, "ASCII")); //$NON-NLS-1$ //$NON-NLS-2$
}
return byteArray;
}
/**
* return the current ide key if we are configured to use the proxy. if
* useProxy is false, you should use the ide key here. It may or may not be
* null.
*
* @return
*/
public String getCurrentIdeKey() {
return currentIdeKey;
}
private void displayErrorMessage(final String message) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
MessageDialog.openError(Display.getDefault().getActiveShell(),
PHPDebugCoreMessages.XDebug_DBGpProxyHandler_3, message);
}
});
}
}