Index: /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/data/validation/OsmValidator.java	(revision 3707)
@@ -46,7 +46,5 @@
 import org.openstreetmap.josm.data.validation.tests.UntaggedWay;
 import org.openstreetmap.josm.data.validation.tests.WronglyOrderedWays;
-import org.openstreetmap.josm.data.validation.util.ValUtil;
 import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
-import org.openstreetmap.josm.gui.dialogs.ValidatorDialog;
 import org.openstreetmap.josm.gui.layer.ValidatorLayer;
 import org.openstreetmap.josm.gui.layer.Layer;
@@ -66,7 +64,4 @@
     /** The validate action */
     public ValidateAction validateAction = new ValidateAction();
-
-    /** The validation dialog */
-    ValidatorDialog validationDialog;
 
     /** Grid detail, multiplier of east,north values for valuable cell sizing */
@@ -103,5 +98,5 @@
 
     public OsmValidator() {
-        checkPluginDir();
+        checkValidatorDir();
         initializeGridDetail();
         initializeTests(getTests());
@@ -110,9 +105,19 @@
 
     /**
+     * Returns the plugin's directory of the plugin
+     *
+     * @return The directory of the plugin
+     */
+    public static String getValidatorDir()
+    {
+        return Main.pref.getPreferencesDir() + "validator/";
+    }
+
+    /**
      * Check if plugin directory exists (store ignored errors file)
      */
-    private void checkPluginDir() {
+    private void checkValidatorDir() {
         try {
-            File pathDir = new File(ValUtil.getPluginDir());
+            File pathDir = new File(getValidatorDir());
             if (!pathDir.exists()) {
                 pathDir.mkdirs();
@@ -127,5 +132,5 @@
         if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) {
             try {
-                final BufferedReader in = new BufferedReader(new FileReader(ValUtil.getPluginDir() + "ignorederrors"));
+                final BufferedReader in = new BufferedReader(new FileReader(getValidatorDir() + "ignorederrors"));
                 for (String line = in.readLine(); line != null; line = in.readLine()) {
                     ignoredErrors.add(line);
@@ -149,5 +154,5 @@
     public static void saveIgnoredErrors() {
         try {
-            final PrintWriter out = new PrintWriter(new FileWriter(ValUtil.getPluginDir() + "ignorederrors"), false);
+            final PrintWriter out = new PrintWriter(new FileWriter(getValidatorDir() + "ignorederrors"), false);
             for (String e : ignoredErrors) {
                 out.println(e);
Index: /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java	(revision 3707)
@@ -48,9 +48,9 @@
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.validation.OsmValidator;
 import org.openstreetmap.josm.data.validation.Severity;
 import org.openstreetmap.josm.data.validation.Test;
 import org.openstreetmap.josm.data.validation.TestError;
 import org.openstreetmap.josm.data.validation.util.Entities;
-import org.openstreetmap.josm.data.validation.util.ValUtil;
 import org.openstreetmap.josm.gui.preferences.ValidatorPreference;
 import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
@@ -199,5 +199,5 @@
         for (String source : sources.split(";")) {
             try {
-                MirroredInputStream s = new MirroredInputStream(source, ValUtil.getPluginDir(), -1);
+                MirroredInputStream s = new MirroredInputStream(source, OsmValidator.getValidatorDir(), -1);
                 InputStreamReader r;
                 try {
Index: /trunk/src/org/openstreetmap/josm/data/validation/util/ValUtil.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/validation/util/ValUtil.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/data/validation/util/ValUtil.java	(revision 3707)
@@ -10,5 +10,4 @@
 import java.util.Set;
 
-import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.Way;
@@ -22,14 +21,4 @@
 public class ValUtil
 {
-    /**
-     * Returns the plugin's directory of the plugin
-     *
-     * @return The directory of the plugin
-     */
-    public static String getPluginDir()
-    {
-        return Main.pref.getPreferencesDir() + "validator/";
-    }
-
     /**
      * Returns the start and end cells of a way.
Index: /trunk/src/org/openstreetmap/josm/gui/MainApplication.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/gui/MainApplication.java	(revision 3707)
@@ -39,4 +39,5 @@
 import org.openstreetmap.josm.io.auth.CredentialsManagerFactory;
 import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
+import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
 import org.openstreetmap.josm.plugins.PluginHandler;
 import org.openstreetmap.josm.plugins.PluginInformation;
@@ -220,4 +221,8 @@
         monitor.worked(1);
 
+        if (RemoteControl.on && RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
+            RemoteControl.start();
+        }
+
         monitor.indeterminateSubTask(tr("Creating main GUI"));
         JFrame mainFrame = new JFrame(tr("Java OpenStreetMap Editor"));
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java	(revision 3707)
@@ -26,4 +26,5 @@
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
 import org.openstreetmap.josm.plugins.PluginDownloadTask;
 import org.openstreetmap.josm.plugins.PluginHandler;
@@ -226,5 +227,5 @@
             PreferenceSetting setting = factory.createPreferenceSetting();
             if (setting != null) {
-                settings.add(factory.createPreferenceSetting());
+                settings.add(setting);
             }
         }
@@ -277,4 +278,7 @@
         settingsFactory.add(new ShortcutPreference.Factory());
         settingsFactory.add(new ValidatorPreference.Factory());
+        if (RemoteControl.on) {
+            settingsFactory.add(new RemoteControlPreference.Factory());
+        }
 
         PluginHandler.getPreferenceSetting(settingsFactory);
Index: /trunk/src/org/openstreetmap/josm/gui/preferences/RemoteControlPreference.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/preferences/RemoteControlPreference.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/gui/preferences/RemoteControlPreference.java	(revision 3707)
@@ -0,0 +1,131 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.preferences;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.GridBagLayout;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import javax.swing.JSeparator;
+import javax.swing.UIManager;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
+import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * Preference settings for the Remote Control plugin
+ *
+ * @author Frederik Ramm
+ */
+public class RemoteControlPreference implements PreferenceSetting
+{
+    public static class Factory implements PreferenceSettingFactory {
+        @Override
+        public PreferenceSetting createPreferenceSetting() {
+            return new RemoteControlPreference();
+        }
+    }
+
+    private JCheckBox enableRemoteControl;
+
+    private JCheckBox permissionLoadData = new JCheckBox(tr("load data from API"));
+    private JCheckBox permissionImportData = new JCheckBox(tr("import data from URL"));
+    private JCheckBox permissionCreateObjects = new JCheckBox(tr("create new objects"));
+    private JCheckBox permissionChangeSelection = new JCheckBox(tr("change the selection"));
+    private JCheckBox permissionChangeViewport = new JCheckBox(tr("change the viewport"));
+    private JCheckBox permissionReadProtocolversion = new JCheckBox(tr("read protocol version"));
+    private JCheckBox alwaysAskUserConfirm = new JCheckBox(tr("confirm all Remote Control actions manually"));
+
+    public void addGui(final PreferenceTabbedPane gui)
+    {
+
+        JPanel remote = gui.createPreferenceTab("remotecontrol.gif", tr("Remote Control"), tr("Settings for the Remote Control freature."));
+
+        remote.add(enableRemoteControl = new JCheckBox(tr("Enable remote control"), RemoteControl.PROP_REMOTECONTROL_ENABLED.get()), GBC.eol());
+
+        final JPanel wrapper = new JPanel();
+        wrapper.setLayout(new GridBagLayout());
+        wrapper.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.gray)));
+
+        remote.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 5));
+
+        final JLabel descLabel = new JLabel("<html>"+
+                tr("The remote control feature allows JOSM to be controlled from other applications, e.g. from a web browser.")
+                + "</html>");
+        wrapper.add(descLabel, GBC.eol().insets(5,5,0,10).fill(GBC.HORIZONTAL));
+        descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN));
+
+        wrapper.add(new JLabel(tr("Permitted actions:")), GBC.eol());
+        int INDENT = 15;
+        wrapper.add(permissionLoadData, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+        wrapper.add(permissionImportData, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+        wrapper.add(permissionChangeSelection, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+        wrapper.add(permissionChangeViewport, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+        wrapper.add(permissionCreateObjects, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+        wrapper.add(permissionReadProtocolversion, GBC.eol().insets(INDENT,5,0,0).fill(GBC.HORIZONTAL));
+
+        wrapper.add(new JSeparator(), GBC.eop().fill(GBC.HORIZONTAL).insets(15, 5, 15, 5));
+
+        wrapper.add(alwaysAskUserConfirm, GBC.eol().fill(GBC.HORIZONTAL));
+
+        final JLabel portLabel = new JLabel("<html>"+tr("JOSM will always listen on port 8111 on localhost." +
+                "The port is not configurable because it is referenced by external applications talking to JOSM.") + "</html>");
+        portLabel.setFont(portLabel.getFont().deriveFont(Font.PLAIN));
+
+        wrapper.add(portLabel, GBC.eol().insets(5,5,0,10).fill(GBC.HORIZONTAL));
+
+        remote.add(Box.createVerticalGlue(), GBC.eol().fill(GBC.VERTICAL));
+
+        permissionLoadData.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.loadDataPermissionKey, LoadAndZoomHandler.loadDataPermissionDefault));
+        permissionImportData.setSelected(Main.pref.getBoolean(ImportHandler.permissionKey, ImportHandler.permissionDefault));
+        permissionChangeSelection.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.changeSelectionPermissionKey, LoadAndZoomHandler.changeSelectionPermissionDefault));
+        permissionChangeViewport.setSelected(Main.pref.getBoolean(LoadAndZoomHandler.changeViewportPermissionKey, LoadAndZoomHandler.changeViewportPermissionDefault));
+        permissionCreateObjects.setSelected(Main.pref.getBoolean(AddNodeHandler.permissionKey, AddNodeHandler.permissionDefault));
+        permissionReadProtocolversion.setSelected(Main.pref.getBoolean(VersionHandler.permissionKey, VersionHandler.permissionDefault));
+        alwaysAskUserConfirm.setSelected(Main.pref.getBoolean(RequestHandler.globalConfirmationKey, RequestHandler.globalConfirmationDefault));
+
+        ActionListener remoteControlEnabled = new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                boolean enabled = enableRemoteControl.isSelected();
+                GuiHelper.setEnabledRec(wrapper, enableRemoteControl.isSelected());
+                // 'setEnabled(false)' does not work for JLabel with html text, so do it manually
+                portLabel.setForeground(enabled ? UIManager.getColor("Label.foreground") : UIManager.getColor("Label.disabledForeground"));
+                descLabel.setForeground(enabled ? UIManager.getColor("Label.foreground") : UIManager.getColor("Label.disabledForeground"));
+                // FIXME: use QuadStateCheckBox to make checkboxes unset when disabled
+            }
+        };
+        enableRemoteControl.addActionListener(remoteControlEnabled);
+        remoteControlEnabled.actionPerformed(null);
+    }
+
+    public boolean ok() {
+        boolean enabled = enableRemoteControl.isSelected();
+        boolean changed = RemoteControl.PROP_REMOTECONTROL_ENABLED.put(enabled);
+        if (enabled) {
+            Main.pref.put(LoadAndZoomHandler.loadDataPermissionKey, permissionLoadData.isSelected());
+            Main.pref.put(ImportHandler.permissionKey, permissionImportData.isSelected());
+            Main.pref.put(LoadAndZoomHandler.changeSelectionPermissionKey, permissionChangeSelection.isSelected());
+            Main.pref.put(LoadAndZoomHandler.changeViewportPermissionKey, permissionChangeViewport.isSelected());
+            Main.pref.put(AddNodeHandler.permissionKey, permissionCreateObjects.isSelected());
+            Main.pref.put(VersionHandler.permissionKey, permissionReadProtocolversion.isSelected());
+            Main.pref.put(RequestHandler.globalConfirmationKey, alwaysAskUserConfirm.isSelected());
+        }
+        // FIXME confirm return value - really no restart needed?
+        return changed;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java	(revision 3706)
+++ /trunk/src/org/openstreetmap/josm/gui/tagging/TaggingPreset.java	(revision 3707)
@@ -7,5 +7,4 @@
 
 import java.awt.Component;
-import java.awt.Container;
 import java.awt.GridBagLayout;
 import java.awt.Image;
@@ -61,4 +60,5 @@
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPritority;
 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
+import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.io.MirroredInputStream;
 import org.openstreetmap.josm.tools.GBC;
@@ -896,5 +896,5 @@
         p.add(items, GBC.eol().fill());
         if (selected.size() == 0 && !supportsRelation()) {
-            setEnabledRec(items, false);
+            GuiHelper.setEnabledRec(items, false);
         }
 
@@ -904,19 +904,4 @@
 
         return p;
-    }
-
-    /**
-     * setEnabled() does not propagate to child elements, so we need this workaround.
-     */
-    static void setEnabledRec(Container root, boolean enabled) {
-        root.setEnabled(enabled);
-        Component children[] = root.getComponents();
-        for(int i = 0; i < children.length; i++) {
-            if(children[i] instanceof Container) {
-                setEnabledRec((Container)children[i], enabled);
-            } else {
-                children[i].setEnabled(enabled);
-            }
-        }
     }
 
Index: /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/gui/util/GuiHelper.java	(revision 3707)
@@ -0,0 +1,26 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.awt.Component;
+import java.awt.Container;
+
+/**
+ * basic gui utils
+ */
+public class GuiHelper {
+    /**
+     * disable / enable a component and all its child components
+     */
+    public static void setEnabledRec(Container root, boolean enabled) {
+        root.setEnabled(enabled);
+        Component[] children = root.getComponents();
+        for (Component child : children) {
+            if(child instanceof Container) {
+                setEnabledRec((Container) child, enabled);
+            } else {
+                child.setEnabled(enabled);
+            }
+        }
+    }
+
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPref.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPref.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPref.java	(revision 3707)
@@ -0,0 +1,25 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol;
+
+/**
+ * Contains a preference name to control permission for the operation
+ * implemented by the RequestHandler, and an error message to be displayed
+ * if not permitted.
+ *
+ * Use @see PermissionPrefWithDefault instead of this class.
+ *
+ * @author Bodo Meissner
+ */
+ @Deprecated
+public class PermissionPref {
+    /** name of the preference setting to permit the remote operation */
+    public String pref;
+    /** message to be displayed if operation is not permitted */
+    public String message;
+
+    public PermissionPref(String pref, String message)
+    {
+        this.pref = pref;
+        this.message = message;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPrefWithDefault.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPrefWithDefault.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/PermissionPrefWithDefault.java	(revision 3707)
@@ -0,0 +1,23 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol;
+
+/**
+ * This class should replace PermissionPref because it allows explicit
+ * specification of the permission's default value.
+ *
+ * @author Bodo Meissner
+ */
+@SuppressWarnings("deprecation")
+public class PermissionPrefWithDefault extends PermissionPref {
+
+    public boolean defaultVal = true;
+
+    public PermissionPrefWithDefault(String pref, boolean defaultVal, String message) {
+        super(pref, message);
+        this.defaultVal = defaultVal;
+    }
+
+    public PermissionPrefWithDefault(PermissionPref prefWithoutDefault) {
+        super(prefWithoutDefault.pref, prefWithoutDefault.message);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControl.java	(revision 3707)
@@ -0,0 +1,73 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol;
+
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
+
+/**
+ * Manager class for remote control operations.
+ *
+ * To allow API there is a @see getVersion() method.
+ *
+ * IMPORTANT! increment the minor version on compatible API extensions
+ * and increment the major version and set minor to 0 on incompatible changes.
+ */
+public class RemoteControl
+{
+    // deactivate the remote control code for now. FIXME: Remove this completely if it gets turned on.
+    static final public boolean on = false;
+
+    /**
+     * If the remote cotrol feature is enabled or disabled. If disabled,
+     * it should not start the server.
+     */
+    public static final BooleanProperty PROP_REMOTECONTROL_ENABLED = new BooleanProperty("remotecontrol.enabled", false);
+
+    /** API version
+     * IMPORTANT! update the version number on API changes.
+     */
+    static final int apiMajorVersion = 1;
+    static final int apiMinorVersion = 0;
+
+    /**
+     * RemoteControl HTTP protocol version. Change minor number for compatible
+     * interface extensions. Change major number in case of incompatible
+     * changes.
+     */
+    static final int protocolMajorVersion = 1;
+    static final int protocolMinorVersion = 2;
+
+    /**
+     * Returns an array of int values with major and minor API version
+     * and major and minor HTTP protocol version.
+     *
+     * The function returns an int[4] instead of an object with fields
+     * to avoid ClassNotFound errors with old versions of remotecontrol.
+     *
+     * @return array of integer version numbers:
+     *    apiMajorVersion, apiMinorVersion, protocolMajorVersion, protocolMajorVersion
+     */
+    public int[] getVersion()
+    {
+        int versions[] = {apiMajorVersion, apiMinorVersion, protocolMajorVersion, protocolMajorVersion};
+        return versions;
+    }
+
+    /**
+     * Start the remote control server
+     */
+    public static void start() {
+        RemoteControlHttpServer.restartRemoteControlHttpServer();
+    }
+
+    /**
+     * Add external external request handler.
+     * Can be used by plugins that want to use remote control.
+     *
+     * @param handler The additional request handler.
+     */
+    public void addRequestHandler(String command, Class<? extends RequestHandler> handlerClass)
+    {
+        RequestProcessor.addRequestHandlerClass(command, handlerClass);
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java	(revision 3707)
@@ -0,0 +1,98 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.InetAddress;
+
+/**
+ * Simple HTTP server that spawns a {@link RequestProcessor} for every
+ * connection.
+ *
+ * Taken from YWMS plugin by frsantos.
+ */
+
+public class RemoteControlHttpServer extends Thread {
+
+    /** Default port for the HTTP server */
+    public static final int DEFAULT_PORT = 8111;
+
+    /** The server socket */
+    private ServerSocket server;
+
+    private static RemoteControlHttpServer instance;
+
+    /**
+     * Starts or restarts the HTTP server
+     */
+    public static void restartRemoteControlHttpServer()
+    {
+        try
+        {
+            if (instance != null)
+                instance.stopServer();
+
+            int port = DEFAULT_PORT;
+            instance = new RemoteControlHttpServer(port);
+            instance.start();
+        }
+        catch(IOException ioe)
+        {
+            ioe.printStackTrace();
+        }
+    }
+
+    /**
+     * Constructor
+     * @param port The port this server will listen on
+     * @throws IOException when connection errors
+     */
+    public RemoteControlHttpServer(int port)
+        throws IOException
+    {
+        super("RemoteControl HTTP Server");
+        this.setDaemon(true);
+        // Start the server socket with only 1 connection.
+        // Also make sure we only listen
+        // on the local interface so nobody from the outside can connect!
+        this.server = new ServerSocket(port, 1,
+            InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
+    }
+
+    /**
+     * The main loop, spawns a {@link RequestProcessor} for each connection
+     */
+    public void run()
+    {
+        System.out.println("RemoteControl::Accepting connections on port " + server.getLocalPort());
+        while (true)
+        {
+            try
+            {
+                Socket request = server.accept();
+                RequestProcessor.processRequest(request);
+            }
+            catch( SocketException se)
+            {
+                if( !server.isClosed() )
+                    se.printStackTrace();
+            }
+            catch (IOException ioe)
+            {
+                ioe.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Stops the HTTP server
+     *
+     * @throws IOException
+     */
+    public void stopServer() throws IOException
+    {
+        server.close();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java	(revision 3707)
@@ -0,0 +1,314 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.Socket;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerErrorException;
+import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException;
+import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
+
+/**
+ * Processes HTTP "remote control" requests.
+ */
+public class RequestProcessor extends Thread {
+    /**
+     * RemoteControl protocol version. Change minor number for compatible
+     * interface extensions. Change major number in case of incompatible
+     * changes.
+     */
+    public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " +
+        RemoteControl.protocolMajorVersion + ", \"minor\": " +
+        RemoteControl.protocolMinorVersion +
+        "}, \"application\": \"JOSM RemoteControl\"}";
+
+    /** The socket this processor listens on */
+    private Socket request;
+
+    /**
+     * Collection of request handlers.
+     * Will be initialized with default handlers here. Other plug-ins
+     * can extend this list by using @see addRequestHandler
+     */
+    private static HashMap<String, Class<? extends RequestHandler>> handlers = new HashMap<String, Class<? extends RequestHandler>>();
+
+    /**
+     * Constructor
+     *
+     * @param request A socket to read the request.
+     */
+    public RequestProcessor(Socket request) {
+        super("RemoteControl request processor");
+        this.setDaemon(true);
+        this.request = request;
+    }
+
+    /**
+     * Spawns a new thread for the request
+     */
+    public static void processRequest(Socket request) {
+        RequestProcessor processor = new RequestProcessor(request);
+        processor.start();
+    }
+
+    /**
+     * Add external request handler. Can be used by other plug-ins that
+     * want to use remote control.
+     *
+     * @param command The command to handle.
+     * @param handler The additional request handler.
+     */
+    static void addRequestHandlerClass(String command,
+            Class<? extends RequestHandler> handler) {
+        addRequestHandlerClass(command, handler, false);
+    }
+
+    /**
+     * Add external request handler. Message can be suppressed.
+     * (for internal use)
+     * 
+     * @param command The command to handle.
+     * @param handler The additional request handler.
+     * @param silent Don't show message if true.
+     */
+    private static void addRequestHandlerClass(String command,
+                Class<? extends RequestHandler> handler, boolean silent) {
+        if(command.charAt(0) == '/')
+        {
+            command = command.substring(1);
+        }
+        String commandWithSlash = "/" + command;
+        if (handlers.get(commandWithSlash) != null) {
+            System.out.println("RemoteControl: ignoring duplicate command " + command
+                    + " with handler " + handler.getName());
+        } else {
+            if (!silent) {
+                System.out.println("RemoteControl: adding command \"" +
+                    command + "\" (handled by " + handler.getSimpleName() + ")");
+            }
+            handlers.put(commandWithSlash, handler);
+        }
+    }
+
+    /** Add default request handlers */
+    static {
+        addRequestHandlerClass(LoadAndZoomHandler.command,
+                LoadAndZoomHandler.class, true);
+        addRequestHandlerClass(LoadAndZoomHandler.command2,
+                LoadAndZoomHandler.class, true);
+        addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
+        addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
+        addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
+    }
+
+    /**
+     * The work is done here.
+     */
+    public void run() {
+        Writer out = null;
+        try {
+            OutputStream raw = new BufferedOutputStream(
+                    request.getOutputStream());
+            out = new OutputStreamWriter(raw);
+            Reader in = new InputStreamReader(new BufferedInputStream(
+                    request.getInputStream()), "ASCII");
+
+            StringBuffer requestLine = new StringBuffer();
+            while (requestLine.length() < 1024) {
+                int c = in.read();
+                if (c == '\r' || c == '\n')
+                    break;
+                requestLine.append((char) c);
+            }
+
+            System.out.println("RemoteControl received: " + requestLine);
+            String get = requestLine.toString();
+            StringTokenizer st = new StringTokenizer(get);
+            if (!st.hasMoreTokens()) {
+                sendError(out);
+                return;
+            }
+            String method = st.nextToken();
+            if (!st.hasMoreTokens()) {
+                sendError(out);
+                return;
+            }
+            String url = st.nextToken();
+
+            if (!method.equals("GET")) {
+                sendNotImplemented(out);
+                return;
+            }
+
+            String command = null;
+            int questionPos = url.indexOf('?');
+            if(questionPos < 0)
+            {
+                command = url;
+            }
+            else
+            {
+                command = url.substring(0, questionPos);
+            }
+
+            // find a handler for this command
+            Class<? extends RequestHandler> handlerClass = handlers
+                    .get(command);
+            if (handlerClass == null) {
+                // no handler found
+                sendBadRequest(out);
+            } else {
+                // create handler object
+                RequestHandler handler = handlerClass.newInstance();
+                try {
+                    handler.setCommand(command);
+                    handler.setUrl(url);
+                    handler.checkPermission();
+                    handler.handle();
+                    sendHeader(out, "200 OK", handler.getContentType(), false);
+                    out.write("Content-length: " + handler.getContent().length()
+                            + "\r\n");
+                    out.write("\r\n");
+                    out.write(handler.getContent());
+                    out.flush();
+                } catch (RequestHandlerErrorException ex) {
+                    sendError(out);
+                } catch (RequestHandlerBadRequestException ex) {
+                    sendBadRequest(out);
+                } catch (RequestHandlerForbiddenException ex) {
+                    sendForbidden(out);
+                }
+            }
+
+        } catch (IOException ioe) {
+        } catch (Exception e) {
+            e.printStackTrace();
+            try {
+                sendError(out);
+            } catch (IOException e1) {
+            }
+        } finally {
+            try {
+                request.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    /**
+     * Sends a 500 error: server error
+     *
+     * @param out
+     *            The writer where the error is written
+     * @throws IOException
+     *             If the error can not be written
+     */
+    private void sendError(Writer out) throws IOException {
+        sendHeader(out, "500 Internal Server Error", "text/html", true);
+        out.write("<HTML>\r\n");
+        out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
+        out.write("</HEAD>\r\n");
+        out.write("<BODY>");
+        out.write("<H1>HTTP Error 500: Internal Server Error</h2>\r\n");
+        out.write("</BODY></HTML>\r\n");
+        out.flush();
+    }
+
+    /**
+     * Sends a 501 error: not implemented
+     *
+     * @param out
+     *            The writer where the error is written
+     * @throws IOException
+     *             If the error can not be written
+     */
+    private void sendNotImplemented(Writer out) throws IOException {
+        sendHeader(out, "501 Not Implemented", "text/html", true);
+        out.write("<HTML>\r\n");
+        out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
+        out.write("</HEAD>\r\n");
+        out.write("<BODY>");
+        out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
+        out.write("</BODY></HTML>\r\n");
+        out.flush();
+    }
+
+    /**
+     * Sends a 403 error: forbidden
+     *
+     * @param out
+     *            The writer where the error is written
+     * @throws IOException
+     *             If the error can not be written
+     */
+    private void sendForbidden(Writer out) throws IOException {
+        sendHeader(out, "403 Forbidden", "text/html", true);
+        out.write("<HTML>\r\n");
+        out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
+        out.write("</HEAD>\r\n");
+        out.write("<BODY>");
+        out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n");
+        out.write("</BODY></HTML>\r\n");
+        out.flush();
+    }
+
+    /**
+     * Sends a 403 error: forbidden
+     *
+     * @param out
+     *            The writer where the error is written
+     * @throws IOException
+     *             If the error can not be written
+     */
+    private void sendBadRequest(Writer out) throws IOException {
+        sendHeader(out, "400 Bad Request", "text/html", true);
+        out.write("<HTML>\r\n");
+        out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
+        out.write("</HEAD>\r\n");
+        out.write("<BODY>");
+        out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
+        out.write("</BODY></HTML>\r\n");
+        out.flush();
+    }
+
+    /**
+     * Send common HTTP headers to the client.
+     *
+     * @param out
+     *            The Writer
+     * @param status
+     *            The status string ("200 OK", "500", etc)
+     * @param contentType
+     *            The content type of the data sent
+     * @param endHeaders
+     *            If true, adds a new line, ending the headers.
+     * @throws IOException
+     *             When error
+     */
+    private void sendHeader(Writer out, String status, String contentType,
+            boolean endHeaders) throws IOException {
+        out.write("HTTP/1.1 " + status + "\r\n");
+        Date now = new Date();
+        out.write("Date: " + now + "\r\n");
+        out.write("Server: JOSM RemoteControl\r\n");
+        out.write("Content-type: " + contentType + "\r\n");
+        out.write("Access-Control-Allow-Origin: *\r\n");
+        if (endHeaders)
+            out.write("\r\n");
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java	(revision 3707)
@@ -0,0 +1,66 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashMap;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
+
+/**
+ * Handler for add_node request.
+ */
+public class AddNodeHandler extends RequestHandler {
+
+    public static final String command = "add_node";
+    public static final String permissionKey = "remotecontrol.permission.create-objects";
+    public static final boolean permissionDefault = false;
+
+    @Override
+    protected void handleRequest() {
+        addNode(args);
+    }
+
+    @Override
+    protected String[] getMandatoryParams()
+    {
+        return new String[] { "lat", "lon" };
+    }
+
+    @Override
+    public String getPermissionMessage() {
+        return tr("Remote Control has been asked to create a new node.");
+    }
+
+    @Override
+    public PermissionPrefWithDefault getPermissionPref()
+    {
+        return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+                "RemoteControl: creating objects forbidden by preferences");
+    }
+
+    /**
+     * Adds a node, implements the GET /add_node?lon=...&amp;lat=... request.
+     * @param args
+     */
+    private void addNode(HashMap<String, String> args){
+
+        // Parse the arguments
+        double lat = Double.parseDouble(args.get("lat"));
+        double lon = Double.parseDouble(args.get("lon"));
+        System.out.println("Adding node at (" + lat + ", " + lon + ")");
+
+        // Create a new node
+        LatLon ll = new LatLon(lat, lon);
+        Node nnew = new Node(ll);
+
+        // Now execute the commands to add this node.
+        Main.main.undoRedo.add(new AddCommand(nnew));
+        Main.main.getCurrentDataSet().setSelected(nnew);
+        Main.map.mapView.repaint();
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java	(revision 3707)
@@ -0,0 +1,51 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.net.URLDecoder;
+
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
+import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
+
+/**
+ * Handler for import request
+ */
+public class ImportHandler extends RequestHandler {
+
+    public static final String command = "import";
+    public static final String permissionKey = "remotecontrol.permission.import";
+    public static final boolean permissionDefault = true;
+
+    @Override
+    protected void handleRequest() throws RequestHandlerErrorException {
+        try {
+            DownloadTask osmTask = new DownloadOsmTask();
+            osmTask.loadUrl(false, URLDecoder.decode(args.get("url"), "UTF-8"), null);
+        } catch (Exception ex) {
+            System.out.println("RemoteControl: Error parsing import remote control request:");
+            ex.printStackTrace();
+            throw new RequestHandlerErrorException();
+        }
+    }
+
+    @Override
+    protected String[] getMandatoryParams()
+    {
+        return new String[] { "url" };
+    }
+
+    @Override
+    public String getPermissionMessage() {
+        return tr("Remote Control has been asked to import data from the following URL:") +
+        "<br>" + request;
+    }
+
+    @Override
+    public PermissionPrefWithDefault getPermissionPref()
+    {
+        return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+                "RemoteControl: import forbidden by preferences");
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java	(revision 3707)
@@ -0,0 +1,178 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+import java.util.HashSet;
+import java.util.concurrent.Future;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.AutoScaleAction;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
+import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
+import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Relation;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
+
+/**
+ * Handler for load_and_zoom request.
+ */
+public class LoadAndZoomHandler extends RequestHandler
+{
+    public static final String command = "load_and_zoom";
+    public static final String command2 = "zoom";
+
+    public static final String loadDataPermissionKey = "remotecontrol.permission.load-data";
+    public static final boolean loadDataPermissionDefault = true;
+    public static final String changeSelectionPermissionKey = "remotecontrol.permission.change-selection";
+    public static final boolean changeSelectionPermissionDefault = true;
+    public static final String changeViewportPermissionKey = "remotecontrol.permission.change-viewport";
+    public static final boolean changeViewportPermissionDefault = true;
+
+    @Override
+    public String getPermissionMessage()
+    {
+        return tr("Remote Control has been asked to load data from the API.") +
+        "<br>" + tr("Request details: {0}", request);
+    }
+
+    @Override
+    protected String[] getMandatoryParams()
+    {
+        return new String[] { "bottom", "top", "left", "right" };
+    }
+
+    @Override
+    protected void handleRequest() throws RequestHandlerErrorException
+    {
+        DownloadTask osmTask = new DownloadOsmTask();
+        double minlat = 0;
+        double maxlat = 0;
+        double minlon = 0;
+        double maxlon = 0;
+        try {
+            minlat = Double.parseDouble(args.get("bottom"));
+            maxlat = Double.parseDouble(args.get("top"));
+            minlon = Double.parseDouble(args.get("left"));
+            maxlon = Double.parseDouble(args.get("right"));
+
+            if(command.equals(myCommand))
+            {
+                if (!Main.pref.getBoolean(loadDataPermissionKey, loadDataPermissionDefault))
+                {
+                    System.out.println("RemoteControl: download forbidden by preferences");
+                }
+                else
+                {
+
+                    // find out whether some data has already been downloaded
+                    Area present = null;
+                    Area toDownload = null;
+                    DataSet ds = Main.main.getCurrentDataSet();
+                    if (ds != null)
+                        present = ds.getDataSourceArea();
+                    if (present != null && !present.isEmpty()) {
+                        toDownload = new Area(new Rectangle2D.Double(minlon,minlat,maxlon-minlon,maxlat-minlat));
+                        toDownload.subtract(present);
+                        if (!toDownload.isEmpty())
+                        {
+                            // the result might not be a rectangle (L shaped etc)
+                            Rectangle2D downloadBounds = toDownload.getBounds2D();
+                            minlat = downloadBounds.getMinY();
+                            minlon = downloadBounds.getMinX();
+                            maxlat = downloadBounds.getMaxY();
+                            maxlon = downloadBounds.getMaxX();
+                        }
+                    }
+                    if((toDownload != null) && toDownload.isEmpty())
+                    {
+                        System.out.println("RemoteControl: no download necessary");
+                    }
+                    else
+                    {
+                        Future<?> future = osmTask.download(false /*no new layer*/, new Bounds(minlat,minlon,maxlat,maxlon), null /* let the task manage the progress monitor */);
+                        Main.worker.submit(new PostDownloadHandler(osmTask, future));
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            System.out.println("RemoteControl: Error parsing load_and_zoom remote control request:");
+            ex.printStackTrace();
+            throw new RequestHandlerErrorException();
+        }
+        if (args.containsKey("select") && Main.pref.getBoolean(changeSelectionPermissionKey, changeSelectionPermissionDefault)) {
+            // select objects after downloading, zoom to selection.
+            final String selection = args.get("select");
+            Main.worker.execute(new Runnable() {
+                public void run() {
+                    HashSet<Long> ways = new HashSet<Long>();
+                    HashSet<Long> nodes = new HashSet<Long>();
+                    HashSet<Long> relations = new HashSet<Long>();
+                    HashSet<OsmPrimitive> newSel = new HashSet<OsmPrimitive>();
+                    for (String item : selection.split(",")) {
+                        if (item.startsWith("way")) {
+                            ways.add(Long.parseLong(item.substring(3)));
+                        } else if (item.startsWith("node")) {
+                            nodes.add(Long.parseLong(item.substring(4)));
+                        } else if (item.startsWith("relation")) {
+                            relations.add(Long.parseLong(item.substring(8)));
+                        } else if (item.startsWith("rel")) {
+                            relations.add(Long.parseLong(item.substring(3)));
+                        } else {
+                            System.out.println("RemoteControl: invalid selection '"+item+"' ignored");
+                        }
+                    }
+                    DataSet ds = Main.main.getCurrentDataSet();
+                    if(ds == null) // e.g. download failed
+                        return;
+                    for (Way w : ds.getWays()) {
+                        if (ways.contains(w.getId())) {
+                            newSel.add(w);
+                        }
+                    }
+                    for (Node n : ds.getNodes()) {
+                        if (nodes.contains(n.getId())) {
+                            newSel.add(n);
+                        }
+                    }
+                    for (Relation r : ds.getRelations()) {
+                        if (relations.contains(r.getId())) {
+                            newSel.add(r);
+                        }
+                    }
+                    ds.setSelected(newSel);
+                    if (Main.pref.getBoolean(changeViewportPermissionKey, changeViewportPermissionDefault))
+                        new AutoScaleAction("selection").actionPerformed(null);
+                }
+            });
+        } else if (Main.pref.getBoolean(changeViewportPermissionKey, changeViewportPermissionDefault)) {
+            // after downloading, zoom to downloaded area.
+            zoom(minlat, maxlat, minlon, maxlon);
+        }
+    }
+
+    protected void zoom(double minlat, double maxlat, double minlon, double maxlon) {
+        final Bounds bounds = new Bounds(new LatLon(minlat, minlon),
+                new LatLon(maxlat, maxlon));
+
+        // make sure this isn't called unless there *is* a MapView
+        //
+        if (Main.map != null && Main.map.mapView != null) {
+            Main.worker.execute(new Runnable() {
+                public void run() {
+                    BoundingXYVisitor bbox = new BoundingXYVisitor();
+                    bbox.visit(bounds);
+                    Main.map.mapView.recalculateCenterScale(bbox);
+                }
+            });
+        }
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java	(revision 3707)
@@ -0,0 +1,230 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.io.remotecontrol.PermissionPref;
+import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
+
+/**
+ * This is the parent of all classes that handle a specific remote control command
+ *
+ * @author Bodo Meissner
+ */
+public abstract class RequestHandler {
+    
+    public static final String globalConfirmationKey = "remotecontrol.always-confirm";
+    public static final boolean globalConfirmationDefault = false;
+
+    /** The GET request arguments */
+    protected HashMap<String,String> args;
+
+    /** The request URL without "GET". */
+    protected String request;
+
+    /** default response */
+    protected String content = "OK\r\n";
+    /** default content type */
+    protected String contentType = "text/plain";
+
+    /** will be filled with the command assigned to the subclass */
+    protected String myCommand;
+
+    /**
+     * Check permission and parameters and handle request.
+     *
+     * @throws RequestHandlerForbiddenException
+     * @throws RequestHandlerBadRequestException
+     * @throws RequestHandlerErrorException
+     */
+    public final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException
+    {
+        checkPermission();
+        checkMandatoryParams();
+        handleRequest();
+    }
+
+    /**
+     * Handle a specific command sent as remote control.
+     *
+     * This method of the subclass will do the real work.
+     *
+     * @throws RequestHandlerErrorException
+     * @throws RequestHandlerBadRequestException
+     */
+    protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException;
+
+    /**
+     * Get a specific message to ask the user for permission for the operation
+     * requested via remote control.
+     *
+     * This message will be displayed to the user if the preference
+     * remotecontrol.always-confirm is true.
+     *
+     * @return the message
+     */
+    abstract public String getPermissionMessage();
+
+    /**
+     * Get a PermissionPref object containing the name of a special permission
+     * preference to individually allow the requested operation and an error
+     * message to be displayed when a disabled operation is requested.
+     *
+     * Default is not to check any special preference. Override this in a
+     * subclass to define permission preference and error message.
+     *
+     * @return the preference name and error message or null
+     */
+    @SuppressWarnings("deprecation")
+    public PermissionPref getPermissionPref()
+    {
+        /* Example:
+        return new PermissionPrefWithDefault("fooobar.remotecontrol",
+        true
+        "RemoteControl: foobar forbidden by preferences");
+        */
+        return null;
+    }
+
+    protected String[] getMandatoryParams()
+    {
+        return null;
+    }
+
+    /**
+     * Check permissions in preferences and display error message
+     * or ask for permission.
+     *
+     * @throws RequestHandlerForbiddenException
+     */
+    @SuppressWarnings("deprecation")
+    final public void checkPermission() throws RequestHandlerForbiddenException
+    {
+        /*
+         * If the subclass defines a specific preference and if this is set
+         * to false, abort with an error message.
+         *
+         * Note: we use the deprecated class here for compatibility with
+         * older versions of WMSPlugin.
+         */
+        PermissionPref permissionPref = getPermissionPref();
+        if((permissionPref != null) && (permissionPref.pref != null))
+        {
+            PermissionPrefWithDefault permissionPrefWithDefault;
+            if(permissionPref instanceof PermissionPrefWithDefault)
+            {
+                permissionPrefWithDefault = (PermissionPrefWithDefault) permissionPref;
+            }
+            else
+            {
+                permissionPrefWithDefault = new PermissionPrefWithDefault(permissionPref);
+            }
+            if (!Main.pref.getBoolean(permissionPrefWithDefault.pref,
+                    permissionPrefWithDefault.defaultVal)) {
+                System.out.println(permissionPrefWithDefault.message);
+                throw new RequestHandlerForbiddenException();
+            }
+        }
+
+        /* Does the user want to confirm everything?
+         * If yes, display specific confirmation message.
+         */
+        if (Main.pref.getBoolean(globalConfirmationKey, globalConfirmationDefault)) {
+            if (JOptionPane.showConfirmDialog(Main.parent,
+                "<html>" + getPermissionMessage() +
+                "<br>" + tr("Do you want to allow this?"),
+                tr("Confirm Remote Control action"),
+                JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
+                    throw new RequestHandlerForbiddenException();
+            }
+        }
+    }
+
+    /**
+     * Set request URL and parse args.
+     *
+     * @param url The request URL.
+     */
+    public void setUrl(String url) {
+        this.request = url;
+        parseArgs();
+    }
+
+    /**
+     * Parse the request parameters as key=value pairs.
+     * The result will be stored in this.args.
+     *
+     * Can be overridden by subclass.
+     */
+    protected void parseArgs() {
+        StringTokenizer st = new StringTokenizer(this.request, "&?");
+        HashMap<String, String> args = new HashMap<String, String>();
+        // ignore first token which is the command
+        if(st.hasMoreTokens()) st.nextToken();
+        while (st.hasMoreTokens()) {
+            String param = st.nextToken();
+            int eq = param.indexOf("=");
+            if (eq > -1)
+                args.put(param.substring(0, eq),
+                         param.substring(eq + 1));
+        }
+        this.args = args;
+    }
+
+    void checkMandatoryParams() throws RequestHandlerBadRequestException {
+        String[] mandatory = getMandatoryParams();
+        if(mandatory == null) return;
+
+        boolean error = false;
+        for (int i = 0; i < mandatory.length; ++i) {
+            String key = mandatory[i];
+            String value = args.get(key);
+            if ((value == null) || (value.length() == 0)) {
+                error = true;
+                System.out.println("'" + myCommand + "' remote control request must have '" + key + "' parameter");
+            }
+        }
+        if (error)
+            throw new RequestHandlerBadRequestException();
+    }
+
+    /**
+     * Save command associated with this handler.
+     *
+     * @param command The command.
+     */
+    public void setCommand(String command)
+    {
+        if (command.charAt(0) == '/') {
+            command = command.substring(1);
+        }
+        myCommand = command;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public String getContentType() {
+        return contentType;
+    }
+
+    public static class RequestHandlerException extends Exception {
+    }
+
+    public static class RequestHandlerErrorException extends RequestHandlerException {
+    }
+
+    public static class RequestHandlerBadRequestException extends RequestHandlerException {
+    }
+    
+    public static class RequestHandlerForbiddenException extends RequestHandlerException {
+        private static final long serialVersionUID = 2263904699747115423L;
+    }
+}
Index: /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java	(revision 3707)
+++ /trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java	(revision 3707)
@@ -0,0 +1,39 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io.remotecontrol.handler;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
+import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
+
+/**
+ * Handler for version request.
+ */
+public class VersionHandler extends RequestHandler {
+
+    public static final String command = "version";
+    public static final String permissionKey = "remotecontrol.permission.read-protocolversion";
+    public static final boolean permissionDefault = true;
+
+    @Override
+    protected void handleRequest() throws RequestHandlerErrorException,
+            RequestHandlerBadRequestException {
+        content = RequestProcessor.PROTOCOLVERSION;
+        contentType = "application/json";
+        if (args.containsKey("jsonp")) {
+            content = args.get("jsonp")+ " && " + args.get("jsonp") + "(" + content + ")";
+        }
+    }
+
+    @Override
+    public String getPermissionMessage() {
+        return tr("Remote Control has been asked to report its protocol version. This enables web sites to detect a running JOSM.");
+    }
+
+    @Override
+    public PermissionPrefWithDefault getPermissionPref()
+    {
+        return new PermissionPrefWithDefault(permissionKey, permissionDefault,
+                "RemoteControl: /version forbidden by preferences");
+    }
+}
