001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.io.remotecontrol.handler;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.UnsupportedEncodingException;
007    import java.net.URLDecoder;
008    import java.text.MessageFormat;
009    import java.util.HashMap;
010    import java.util.LinkedList;
011    import java.util.List;
012    
013    import javax.swing.JOptionPane;
014    
015    import org.openstreetmap.josm.Main;
016    import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
017    import org.openstreetmap.josm.tools.Utils;
018    
019    /**
020     * This is the parent of all classes that handle a specific remote control command
021     *
022     * @author Bodo Meissner
023     */
024    public abstract class RequestHandler {
025    
026        public static final String globalConfirmationKey = "remotecontrol.always-confirm";
027        public static final boolean globalConfirmationDefault = false;
028        public static final String loadInNewLayerKey = "remotecontrol.new-layer";
029        public static final boolean loadInNewLayerDefault = false;
030    
031        /** The GET request arguments */
032        protected HashMap<String,String> args;
033    
034        /** The request URL without "GET". */
035        protected String request;
036    
037        /** default response */
038        protected String content = "OK\r\n";
039        /** default content type */
040        protected String contentType = "text/plain";
041    
042        /** will be filled with the command assigned to the subclass */
043        protected String myCommand;
044    
045        /**
046         * Check permission and parameters and handle request.
047         *
048         * @throws RequestHandlerForbiddenException
049         * @throws RequestHandlerBadRequestException
050         * @throws RequestHandlerErrorException
051         */
052        public final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException
053        {
054            checkMandatoryParams();
055            checkPermission();
056            handleRequest();
057        }
058    
059        /**
060         * Handle a specific command sent as remote control.
061         *
062         * This method of the subclass will do the real work.
063         *
064         * @throws RequestHandlerErrorException
065         * @throws RequestHandlerBadRequestException
066         */
067        protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException;
068    
069        /**
070         * Get a specific message to ask the user for permission for the operation
071         * requested via remote control.
072         *
073         * This message will be displayed to the user if the preference
074         * remotecontrol.always-confirm is true.
075         *
076         * @return the message
077         */
078        abstract public String getPermissionMessage();
079    
080        /**
081         * Get a PermissionPref object containing the name of a special permission
082         * preference to individually allow the requested operation and an error
083         * message to be displayed when a disabled operation is requested.
084         *
085         * Default is not to check any special preference. Override this in a
086         * subclass to define permission preference and error message.
087         *
088         * @return the preference name and error message or null
089         */
090        abstract public PermissionPrefWithDefault getPermissionPref();
091    
092        abstract public String[] getMandatoryParams();
093    
094        /**
095         * Check permissions in preferences and display error message
096         * or ask for permission.
097         *
098         * @throws RequestHandlerForbiddenException
099         */
100        final public void checkPermission() throws RequestHandlerForbiddenException
101        {
102            /*
103             * If the subclass defines a specific preference and if this is set
104             * to false, abort with an error message.
105             *
106             * Note: we use the deprecated class here for compatibility with
107             * older versions of WMSPlugin.
108             */
109            PermissionPrefWithDefault permissionPref = getPermissionPref();
110            if((permissionPref != null) && (permissionPref.pref != null))
111            {
112                if (!Main.pref.getBoolean(permissionPref.pref, permissionPref.defaultVal)) {
113                    String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by preferences", myCommand);
114                    System.out.println(err);
115                    throw new RequestHandlerForbiddenException(err);
116                }
117            }
118    
119            /* Does the user want to confirm everything?
120             * If yes, display specific confirmation message.
121             */
122            if (Main.pref.getBoolean(globalConfirmationKey, globalConfirmationDefault)) {
123                if (JOptionPane.showConfirmDialog(Main.parent,
124                    "<html>" + getPermissionMessage() +
125                    "<br>" + tr("Do you want to allow this?"),
126                    tr("Confirm Remote Control action"),
127                    JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
128                        String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by user''s choice", myCommand);
129                        throw new RequestHandlerForbiddenException(err);
130                }
131            }
132        }
133    
134        /**
135         * Set request URL and parse args.
136         *
137         * @param url The request URL.
138         */
139        public void setUrl(String url) {
140            this.request = url;
141            parseArgs();
142        }
143    
144        /**
145         * Parse the request parameters as key=value pairs.
146         * The result will be stored in {@code this.args}.
147         *
148         * Can be overridden by subclass.
149         */
150        protected void parseArgs() {
151            try {
152                String req = URLDecoder.decode(this.request, "UTF-8");
153                HashMap<String, String> args = new HashMap<String, String>();
154                if (req.indexOf('?') != -1) {
155                    String query = req.substring(req.indexOf('?') + 1);
156                    if (query.indexOf('#') != -1) {
157                        query = query.substring(0, query.indexOf('#'));
158                    }
159                    String[] params = query.split("&", -1);
160                    for (String param : params) {
161                        int eq = param.indexOf('=');
162                        if (eq != -1) {
163                            args.put(param.substring(0, eq), param.substring(eq + 1));
164                        }
165                    }
166                }
167                this.args = args;
168            } catch (UnsupportedEncodingException ex) {
169                throw new IllegalStateException(ex);
170            }
171        }
172    
173        void checkMandatoryParams() throws RequestHandlerBadRequestException {
174            String[] mandatory = getMandatoryParams();
175            if(mandatory == null) return;
176    
177            List<String> missingKeys = new LinkedList<String>();
178            boolean error = false;
179            for (String key : mandatory) {
180                String value = args.get(key);
181                if ((value == null) || (value.length() == 0)) {
182                    error = true;
183                    System.out.println("'" + myCommand + "' remote control request must have '" + key + "' parameter");
184                    missingKeys.add(key);
185                }
186            }
187            if (error) {
188                throw new RequestHandlerBadRequestException(
189                        "The following keys are mandatory, but have not been provided: "
190                        + Utils.join(", ", missingKeys));
191            }
192        }
193    
194        /**
195         * Save command associated with this handler.
196         *
197         * @param command The command.
198         */
199        public void setCommand(String command)
200        {
201            if (command.charAt(0) == '/') {
202                command = command.substring(1);
203            }
204            myCommand = command;
205        }
206    
207        public String getContent() {
208            return content;
209        }
210    
211        public String getContentType() {
212            return contentType;
213        }
214    
215        protected boolean isLoadInNewLayer() {
216            return args.get("new_layer") != null && !args.get("new_layer").isEmpty()
217                    ? Boolean.parseBoolean(args.get("new_layer"))
218                    : Main.pref.getBoolean(loadInNewLayerKey, loadInNewLayerDefault);
219        }
220    
221        public static class RequestHandlerException extends Exception {
222    
223            public RequestHandlerException(String message) {
224                super(message);
225            }
226    
227            public RequestHandlerException() {
228            }
229        }
230    
231        public static class RequestHandlerErrorException extends RequestHandlerException {
232        }
233    
234        public static class RequestHandlerBadRequestException extends RequestHandlerException {
235    
236            public RequestHandlerBadRequestException(String message) {
237                super(message);
238            }
239        }
240    
241        public static class RequestHandlerForbiddenException extends RequestHandlerException {
242            private static final long serialVersionUID = 2263904699747115423L;
243    
244            public RequestHandlerForbiddenException(String message) {
245                super(message);
246            }
247        }
248    }