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 }