### Eclipse Workspace Patch 1.0 #P org.eclipse.ui.ide.application Index: META-INF/MANIFEST.MF =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.ide.application/META-INF/MANIFEST.MF,v retrieving revision 1.8 diff -u -r1.8 MANIFEST.MF --- META-INF/MANIFEST.MF 1 May 2008 14:01:17 -0000 1.8 +++ META-INF/MANIFEST.MF 2 Jul 2008 21:03:53 -0000 @@ -13,7 +13,8 @@ org.eclipse.ui.navigator.resources;bundle-version="[3.2.100,4.0.0)", org.eclipse.core.net;bundle-version="[1.0.0,2.0.0)", org.eclipse.update.core;bundle-version="[3.1.100,4.0.0)", - com.ibm.icu;bundle-version="3.8.1" + com.ibm.icu;bundle-version="3.8.1", + org.eclipse.core.filesystem;bundle-version="1.2.0" Export-Package: org.eclipse.ui.internal.ide.application;x-internal:=true, org.eclipse.ui.internal.ide.application.dialogs;x-internal:=true Bundle-RequiredExecutionEnvironment: J2SE-1.4 Index: src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java,v retrieving revision 1.13 diff -u -r1.13 IDEWorkbenchAdvisor.java --- src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java 1 May 2008 14:01:17 -0000 1.13 +++ src/org/eclipse/ui/internal/ide/application/IDEWorkbenchAdvisor.java 2 Jul 2008 21:03:53 -0000 @@ -16,7 +16,7 @@ import java.util.Iterator; import java.util.Map; import java.util.TreeMap; - +import org.eclipse.core.filesystem.EFS; import org.eclipse.core.net.proxy.IProxyService; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IResource; @@ -27,6 +27,7 @@ import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IBundleGroup; import org.eclipse.core.runtime.IBundleGroupProvider; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; @@ -46,6 +47,9 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Listener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.application.IWorkbenchConfigurer; import org.eclipse.ui.application.IWorkbenchWindowConfigurer; @@ -128,7 +132,7 @@ private IDEIdleHelper idleHelper; private Listener settingsChangeListener; - + /** * Support class for monitoring workspace changes and periodically * validating the undo history @@ -140,6 +144,10 @@ */ private AbstractStatusHandler ideWorkbenchErrorHandler; + static IDEWorkbenchAdvisor getInstance() { + return workbenchAdvisor; + } + /** * Creates a new workbench advisor instance. */ @@ -159,7 +167,7 @@ public void initialize(IWorkbenchConfigurer configurer) { PluginActionBuilder.setAllowIdeLogging(true); - + // make sure we always save and restore workspace state configurer.setSaveAndRestore(true); @@ -194,7 +202,7 @@ // initialize idle handler idleHelper = new IDEIdleHelper(configurer); - + // initialize the workspace undo monitor workspaceUndoMonitor = WorkspaceUndoMonitor.getInstance(); @@ -242,12 +250,51 @@ initializeSettingsChangeListener(); Display.getCurrent().addListener(SWT.Settings, settingsChangeListener); + runStartupCommand(Platform.getCommandLineArgs()); } finally {// Resume background jobs after we startup Job.getJobManager().resume(); } } /** + * Runs a command specified on the command line, if any + */ + boolean runStartupCommand(String[] arguments) { + if (arguments.length == 0) + return false; + // if the last command line argument is a file, open that file in an + // editor + final IPath path = new Path(arguments[arguments.length - 1]); + if (!path.toFile().exists()) + return false; + IWorkbenchWindow window = PlatformUI.getWorkbench() + .getActiveWorkbenchWindow(); + if (window == null) { + IWorkbenchWindow[] windows = PlatformUI.getWorkbench() + .getWorkbenchWindows(); + if (windows.length > 0) + window = windows[0]; + } + if (window == null) + return false; + final IWorkbenchPage page = window.getActivePage(); + if (page == null) + return false; + window.getShell().getDisplay().syncExec(new Runnable() { + public void run() { + try { + IDE.openEditorOnFileStore(page, EFS.getLocalFileSystem() + .getStore(path)); + } catch (PartInitException e) { + IDEWorkbenchPlugin.log( + "Error opening editor on path: " + path, e); //$NON-NLS-1$ + } + } + }); + return true; + } + + /** * Activate the proxy service by obtaining it. */ private void activateProxyService() { @@ -260,7 +307,7 @@ } if (proxyService == null) { IDEWorkbenchPlugin.log("Proxy service could not be found."); //$NON-NLS-1$ - } + } } /** @@ -697,7 +744,7 @@ declareWorkbenchImage(ideBundle, IDEInternalWorkbenchImages.IMG_ETOOL_PROBLEM_CATEGORY, PATH_ETOOL + "problem_category.gif", true); //$NON-NLS-1$ - + // synchronization indicator objects // declareRegistryImage(IDEInternalWorkbenchImages.IMG_OBJS_WBET_STAT, // PATH_OVERLAY+"wbet_stat.gif"); Index: src/org/eclipse/ui/internal/ide/application/IDEApplication.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.ide.application/src/org/eclipse/ui/internal/ide/application/IDEApplication.java,v retrieving revision 1.7 diff -u -r1.7 IDEApplication.java --- src/org/eclipse/ui/internal/ide/application/IDEApplication.java 3 Jun 2008 17:56:02 -0000 1.7 +++ src/org/eclipse/ui/internal/ide/application/IDEApplication.java 2 Jul 2008 21:03:53 -0000 @@ -105,7 +105,7 @@ Platform.endSplash(); return EXIT_OK; } - + // create the workbench with this advisor and run it until it exits // N.B. createWorkbench remembers the advisor, and also registers // the workbench globally so that all UI plug-ins can find it using @@ -217,6 +217,15 @@ // -data @noDefault or -data not specified, prompt and set ChooseWorkspaceData launchData = new ChooseWorkspaceData(instanceLoc .getDefault()); + + //check if someone is listening that we can pass our command line to + if (launchData.getPort() != 0) { + if (new IDECommunication().writeToPort(launchData.getPort(), launchData.getIPCAuthorization())) { + //we successfully punted this application's command line to another instance, + //so we can just shutdown this instance. + return false; + } + } boolean force = false; while (true) { @@ -233,6 +242,11 @@ // the operation will fail if the url is not a valid // instance data area, so other checking is unneeded if (instanceLoc.setURL(workspaceUrl, true)) { + int[] ipcData = new IDECommunication().listen(); + if (ipcData != null) { + launchData.setPort(ipcData[0]); + launchData.setIPCAuthorization(ipcData[1]); + } launchData.writePersistedData(); writeWorkspaceVersion(); return true; Index: src/org/eclipse/ui/internal/ide/application/IDECommunication.java =================================================================== RCS file: src/org/eclipse/ui/internal/ide/application/IDECommunication.java diff -N src/org/eclipse/ui/internal/ide/application/IDECommunication.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/ui/internal/ide/application/IDECommunication.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + ******************************************************************************/ + +package org.eclipse.ui.internal.ide.application; + +import java.security.SecureRandom; + +import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; + +import java.io.*; +import java.net.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; + +/** + * A helper class that deals with interprocess communication between applications. + *

+ * This class uses a socket connection on a random available port to establish + * communication between applications. The client must provide an authorization + * code to protect against communication from arbitrary port sniffing applications. + * + * @since 3.5 + */ +class IDECommunication { + /** + * A job that listens on a socket for a command, and then invokes the + * workbench advisor to handle the command. + */ + class ListenJob extends Job { + private ServerSocket server; + private int auth; + + public ListenJob(ServerSocket server, int auth) { + super("Listen"); //$NON-NLS-1$ + this.server = server; + this.auth = auth; + setSystem(true); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#canceling() + */ + protected void canceling() { + try { + server.close(); + } catch (IOException e) { + //ignore + } + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + protected IStatus run(IProgressMonitor monitor) { + if (server.isClosed()) + return Status.OK_STATUS; + Socket client = null; + DataInputStream in = null; + DataOutputStream out = null; + try { + client = server.accept(); + in = new DataInputStream(client.getInputStream()); + out = new DataOutputStream(client.getOutputStream()); + //read the protocol version + int version = in.readInt(); + String[] args = null; + if (version == PROTOCOL_VERSION_ONE) { + //read the authorization checksum + int receivedAuth = in.readInt(); + //abort immediately if we failed to read the authorization integer + if (receivedAuth != auth) { + schedule(SCHEDULE_DELAY); + return new Status(IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, "Connection from unauthorized client on port " + server.getLocalPort()); //$NON-NLS-1$ + } + args = new String[in.readInt()]; + for (int i = 0; i < args.length; i++) { + args[i] = in.readUTF(); + } + } + //pass the read arguments to the workbench advisor + IDEWorkbenchAdvisor advisor = IDEWorkbenchAdvisor.getInstance(); + if (advisor != null && args != null) { + if (advisor.runStartupCommand(args)) + out.writeUTF(OK); + else + out.writeUTF(FAIL); + } + } catch (IOException e) { + IDEWorkbenchPlugin.log("Error communicating with client", e); //$NON-NLS-1$ + } finally { + safeClose(in); + safeClose(out); + safeClose(client); + } + //reschedule + schedule(SCHEDULE_DELAY); + return Status.OK_STATUS; + } + } + + /** + * The first communication protocol version. This version contains an authorization + * integer followed by a list of command line arguments. The format is: int authorizationId, int N, String[N]. + */ + private static final int PROTOCOL_VERSION_ONE = 1; + + private static final long SCHEDULE_DELAY = 1000; + + static final String OK = "OK"; //$NON-NLS-1$ + static final String FAIL = "FAIL"; //$NON-NLS-1$ + + /** + * Start listening for commands. + * @return An integer array of size two. The first integer is the port + * number we are listening on, and the second integer is an authorization + * integer used to validate connections. Returns null if + * there was a failure to establish communication + */ + public int[] listen() { + try { + ServerSocket server = new ServerSocket(0); + int auth = new SecureRandom().nextInt(); + new ListenJob(server, auth).schedule(SCHEDULE_DELAY); + return new int[] {server.getLocalPort(), auth}; + } catch (IOException e) { + return null; + } + } + + /** + * Close the given stream and ignore secondary failures. + */ + void safeClose(DataInputStream in) { + try { + if (in != null) + in.close(); + } catch (IOException e) { + //ignore secondary failure while closing + } + } + + /** + * Close the given stream and ignore secondary failures. + */ + void safeClose(DataOutputStream out) { + try { + if (out != null) + out.close(); + } catch (IOException e) { + //ignore secondary failure while closing + } + } + + /** + * Close the given socket and ignore secondary failures. + */ + void safeClose(Socket socket) { + try { + if (socket != null) + socket.close(); + } catch (IOException e) { + //ignore secondary failure while closing + } + } + + /** + * Writes this application's command line arguments to the given port. + * + * @param port The port to write data to + * @param auth The authorization integer + * @return true if written successfully, and false otherwise. + */ + public boolean writeToPort(int port, int auth) { + Socket socket = null; + DataOutputStream out = null; + DataInputStream in = null; + try { + socket = new Socket("localhost", port);//$NON-NLS-1$ + out = new DataOutputStream(socket.getOutputStream()); + in = new DataInputStream(socket.getInputStream()); + out.writeInt(PROTOCOL_VERSION_ONE); + out.writeInt(auth); + String[] args = Platform.getCommandLineArgs(); + out.writeInt(args.length); + for (int i = 0; i < args.length; i++) + out.writeUTF(args[i]); + //server will return OK or FAIL + return in.readUTF().equals(OK); + } catch (UnknownHostException e) { + return false; + } catch (IOException e) { + return false; + } finally { + safeClose(socket); + safeClose(out); + safeClose(in); + } + } + +} #P org.eclipse.ui.ide Index: src/org/eclipse/ui/internal/ide/ChooseWorkspaceData.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.ui.ide/src/org/eclipse/ui/internal/ide/ChooseWorkspaceData.java,v retrieving revision 1.21 diff -u -r1.21 ChooseWorkspaceData.java --- src/org/eclipse/ui/internal/ide/ChooseWorkspaceData.java 24 Mar 2008 19:13:35 -0000 1.21 +++ src/org/eclipse/ui/internal/ide/ChooseWorkspaceData.java 2 Jul 2008 21:03:54 -0000 @@ -69,7 +69,7 @@ /** * This is the second version of the encode/decode protocol that uses the * confi area preferences store for persistence. This version is the same as - * the previous version except it uses a \n character to seperate the path + * the previous version except it uses a \n character to separate the path * entries instead of commas. (see bug 98467) * * @since 3.3.1 @@ -83,6 +83,18 @@ private String selection; private String[] recentWorkspaces; + + /** + * The port on which an instance of this configuration is already listening for remote communication. + * This value is 0 if nobody is already listening. + */ + private int port = 0; + /** + * An authorization integer that a client must provide when communication with + * this application. This is a simple security measure to prevent a port scanner + * from executing commands in this application. + */ + private int ipcAuth = 0; // xml tags private static interface XML { @@ -101,7 +113,11 @@ public static final String MAX_LENGTH = "maxLength"; //$NON-NLS-1$ public static final String PATH = "path"; //$NON-NLS-1$ - } + + public static final String PORT= "port"; //$NON-NLS-1$ + + public static final String IPC_AUTH= "ipcAuth"; //$NON-NLS-1$ +} /** * Creates a new instance, loading persistent data if its found. @@ -151,6 +167,23 @@ } initialDefault = dir; } + + /** + * Sets the port on which an instance is listening for remote communication. + * @param port The port, or 0 to indicate not listening. + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Sets an authorization integer that a client must provide to establish + * remote communication. This is used to prevent a port sniffer from + * executing commands. + */ + public void setIPCAuthorization(int auth) { + this.ipcAuth = auth; + } /** * Return the currently selected workspace or null if nothing is selected. @@ -173,6 +206,23 @@ public String[] getRecentWorkspaces() { return recentWorkspaces; } + + /** + * Returns the port on which an instance of this same configuration is + * already listening for remote communication, or 0 if nobody is listening. + * @return The port number, or 0. + */ + public int getPort() { + return port; + } + + /** + * Returns the authorization integer for establishing interprocess communication. + * @return The authorization integer + */ + public int getIPCAuthorization() { + return ipcAuth; + } /** * The argument workspace has been selected, update the receiver. Does not @@ -230,8 +280,14 @@ // 5. store the protocol version used to encode the list node.putInt(IDE.Preferences.RECENT_WORKSPACES_PROTOCOL, PERS_ENCODING_VERSION_CONFIG_PREFS_NO_COMMAS); + + // 6. store the interprocess communication data, if any + if (port != 0) { + node.putInt(XML.PORT, port); + node.putInt(XML.IPC_AUTH, ipcAuth); + } - // 6. store the node + // 7. store the node try { node.flush(); } catch (BackingStoreException e) { @@ -405,6 +461,10 @@ .getString(IDE.Preferences.RECENT_WORKSPACES); recentWorkspaces = decodeStoredWorkspacePaths(protocol, max, workspacePathPref); + // 5. load the interprocess communication data + port = store.getInt(XML.PORT); + ipcAuth = store.getInt(XML.IPC_AUTH); + return true; }