/******************************************************************************* * 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.zend.communication; import static org.eclipse.php.internal.debug.core.zend.communication.BroadcastConnection.ConnectionConstants.*; import java.io.*; import java.net.Socket; import java.net.URLDecoder; import java.text.MessageFormat; import java.util.*; 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.core.runtime.preferences.InstanceScope; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.php.internal.core.util.VersionUtils; import org.eclipse.php.internal.debug.core.PHPDebugPlugin; import org.eclipse.php.internal.debug.core.PHPDebugUtil; 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.preferences.PHPDebugCorePreferenceNames; import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerServerSettings; import org.eclipse.php.internal.debug.core.zend.debugger.ZendDebuggerSettingsUtil; import org.eclipse.php.internal.server.core.Server; import org.eclipse.php.internal.server.core.manager.ServersManager; /** * Broadcast connection for handling any "external" request that arrives from * the Zend debugger engine. * * @author Shalom Gibly */ public class BroadcastConnection { /** * Connection handler job. */ protected class ConnectionHandler extends Job { public ConnectionHandler() { super(Messages.BroadcastConnection_Broadcast_connection_handler_name); setSystem(true); setUser(false); setPriority(LONG); } @Override protected IStatus run(IProgressMonitor monitor) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream)); List<String> requestLines = new ArrayList<String>(); String currentLine; while (true) { currentLine = reader.readLine(); if (currentLine == null || currentLine.isEmpty()) break; requestLines.add(currentLine); } String firstLine = requestLines.isEmpty() ? null : requestLines.get(0); String host; int port; Server server = lookupServer(requestLines); host = lookupHosts(server); port = lookupPort(server); writer.write(getStandardHTTPResponse()); if (isRequestFromZendServer(firstLine)) { writer.println(getJSonResponseString(false, host, port)); } else { String platform = getPlatformValue(firstLine); if (platform == null) { writer.println(getResponseString(false, host, port)); } else { writer.write(getHTMLContent(platform, host, port)); } } writer.flush(); } catch (Exception e) { DebugPlugin.log(e); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException oe) { DebugPlugin.log(oe); } } if (socket != null) { try { socket.close(); } catch (IOException ioe) { DebugPlugin.log(ioe); } } } return Status.OK_STATUS; } } /** * Simple JSON object descriptor. */ protected static class JSONDescriptor { protected Map<String, String> properties = new HashMap<String, String>(); public JSONDescriptor() { }; public void put(String key, String value) { properties.put(key, value); } public String getString() { try { Iterator<String> keys = properties.keySet().iterator(); StringBuilder sb = new StringBuilder("{"); //$NON-NLS-1$ while (keys.hasNext()) { if (sb.length() > 1) { sb.append(','); } Object o = keys.next(); sb.append(quote(o.toString())); sb.append(':'); sb.append(properties.get(o)); } sb.append('}'); return sb.toString(); } catch (Exception e) { return null; } } public static String quote(String string) { if (string == null || string.length() == 0) { return "''"; //$NON-NLS-1$ } char b; char c = 0; int i; int len = string.length(); StringBuilder sb = new StringBuilder(len + 4); String t; sb.append('\''); for (i = 0; i < len; i += 1) { b = c; c = string.charAt(i); switch (c) { case '\\': case '\'': sb.append('\\'); sb.append(c); break; case '/': if (b == '<') { sb.append('\\'); } sb.append(c); break; case '\b': sb.append("\\b"); //$NON-NLS-1$ break; case '\t': sb.append("\\t"); //$NON-NLS-1$ break; case '\n': sb.append("\\n"); //$NON-NLS-1$ break; case '\f': sb.append("\\f"); //$NON-NLS-1$ break; case '\r': sb.append("\\r"); //$NON-NLS-1$ break; default: if (c < ' ') { t = "000" + Integer.toHexString(c); //$NON-NLS-1$ sb.append("\\u" + t.substring(t.length() - 4)); //$NON-NLS-1$ } else { sb.append(c); } } } sb.append('\''); return sb.toString(); } } /** * Set of different constant strings for broadcast connection. */ public interface ConnectionConstants { public static final String DEBUG_HOST = "debug_host";//$NON-NLS-1$ public static final String DEBUG_PORT = "debug_port";//$NON-NLS-1$ public static final String DEBUG_FASTFILE = "debug_fastfile";//$NON-NLS-1$ public static final String USE_SSL = "use_ssl";//$NON-NLS-1$ public static final String USE_TUNNELING = "use_tunneling";//$NON-NLS-1$ public static final String IDE_SETTINGS = "var zendStudioSettings = "; //$NON-NLS-1$ public static final String PLATFORM_GUI = "platform_gui="; //$NON-NLS-1$ public static final String REFERER = "Referer:"; //$NON-NLS-1$ public static final String STANDARD_HTTP_RESPONSE = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"; //$NON-NLS-1$ } protected Socket socket; /** * Constructs a new BroadcastConnection with a given {@link Socket}. * * @param socket */ public BroadcastConnection(Socket socket) { init(socket); } protected void init(Socket socket) { this.socket = socket; (new ConnectionHandler()).schedule(); } protected boolean isRequestFromZendServer(String line) { if (line == null) return false; String zendServerString = "ZendServer="; //$NON-NLS-1$ int index = line.indexOf(zendServerString); if (index == -1) return false; String value = null; value = line.substring(index + zendServerString.length()); index = value.indexOf("HTTP");//$NON-NLS-1$ if (index == -1) return false; value = value.substring(0, index); return isBiggerOrEqual(value, "4.0.0");//$NON-NLS-1$ } protected boolean isBiggerOrEqual(String value, String baseVersion) { if (value == null) return false; return VersionUtils.equal(value, baseVersion, 3) || VersionUtils.greater(value, baseVersion, 3); } protected String getPlatformValue(String line) { if (line == null) return null; int index = line.indexOf(PLATFORM_GUI); if (index == -1) return null; String value = null; try { value = line.substring(index + PLATFORM_GUI.length()); value = value.substring(0, value.indexOf(" ")); //$NON-NLS-1$ value = URLDecoder.decode(value, "UTF-8"); //$NON-NLS-1$ } catch (Exception e) { DebugPlugin.log(e); return null; } return value; } protected String getStandardHTTPResponse() { return STANDARD_HTTP_RESPONSE; } protected String getResponseString(boolean addQuestionMark, String host, int port) { int sslOn = isUseSSL() ? 1 : 0; String result = addQuestionMark ? "?" : "&"; //$NON-NLS-1$ //$NON-NLS-2$ result += DEBUG_PORT + '=' + port; result += '&' + DEBUG_HOST + '=' + host; result += '&' + DEBUG_FASTFILE + '=' + String.valueOf(1); if (isUseSSL()) result += '&' + USE_SSL + '=' + String.valueOf(sslOn); return result; } protected String getJSonResponseString(boolean addQuestionMark, String host, int port) { String result = null; int sslOn = isUseSSL() ? 1 : 0; JSONDescriptor descriptor = new JSONDescriptor(); descriptor.put(DEBUG_HOST, JSONDescriptor.quote(host)); descriptor.put(DEBUG_PORT, String.valueOf(port)); descriptor.put(DEBUG_FASTFILE, String.valueOf(1)); if (isUseSSL()) descriptor.put(USE_SSL, String.valueOf(sslOn)); result = IDE_SETTINGS + descriptor.getString(); return result; } protected String getHTMLContent(String platformValue, String host, int port) { String platformPath = platformValue; // There is no http:// or https:// in the value if (platformValue.indexOf("://") == -1) //$NON-NLS-1$ platformPath = "http://" + platformValue; //$NON-NLS-1$ // Add question mark if not exists platformPath += getResponseString(platformValue.indexOf('?') == -1, host, port); String htmlContent = MessageFormat.format(Messages.BroadcastConnection_HTML_content_message, platformPath, platformPath); return htmlContent; } protected Server lookupServer(List<String> requestLines) { Iterator<String> iterator = requestLines.iterator(); while (iterator.hasNext()) { String currentLine = iterator.next().trim(); if (currentLine.startsWith(REFERER)) { String referer = currentLine.replaceFirst(REFERER, "").trim(); //$NON-NLS-1$ return ServersManager.findByURL(referer); } } return null; } protected String lookupHosts(Server server) { if (server != null) { IDebuggerSettings settings = DebuggerSettingsManager.INSTANCE.findSettings(server.getUniqueId(), server.getDebuggerId()); if (settings instanceof ZendDebuggerServerSettings) { return ZendDebuggerSettingsUtil.getDebugHosts(server.getUniqueId()); } } return PHPDebugUtil.getZendAllHosts(); } protected int lookupPort(Server server) { if (server != null) { IDebuggerSettings settings = DebuggerSettingsManager.INSTANCE.findSettings(server.getUniqueId(), server.getDebuggerId()); if (settings instanceof ZendDebuggerServerSettings) { return ZendDebuggerSettingsUtil.getDebugPort(server.getUniqueId()); } } return PHPDebugPlugin.getDebugPort(DebuggerCommunicationDaemon.ZEND_DEBUGGER_ID); } private boolean isUseSSL() { return InstanceScope.INSTANCE.getNode(PHPDebugPlugin.ID) .getBoolean(PHPDebugCorePreferenceNames.ZEND_DEBUG_ENCRYPTED_SSL_DATA, false); } }