source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java@ 12846

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 14.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol.handler;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.net.URI;
7import java.net.URISyntaxException;
8import java.text.MessageFormat;
9import java.util.Collections;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16
17import javax.swing.JLabel;
18import javax.swing.JOptionPane;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
22import org.openstreetmap.josm.spi.preferences.Config;
23import org.openstreetmap.josm.tools.Logging;
24import org.openstreetmap.josm.tools.Utils;
25
26/**
27 * This is the parent of all classes that handle a specific remote control command
28 *
29 * @author Bodo Meissner
30 */
31public abstract class RequestHandler {
32
33 public static final String globalConfirmationKey = "remotecontrol.always-confirm";
34 public static final boolean globalConfirmationDefault = false;
35 public static final String loadInNewLayerKey = "remotecontrol.new-layer";
36 public static final boolean loadInNewLayerDefault = false;
37
38 /** The GET request arguments */
39 protected Map<String, String> args;
40
41 /** The request URL without "GET". */
42 protected String request;
43
44 /** default response */
45 protected String content = "OK\r\n";
46 /** default content type */
47 protected String contentType = "text/plain";
48
49 /** will be filled with the command assigned to the subclass */
50 protected String myCommand;
51
52 /**
53 * who sent the request?
54 * the host from referer header or IP of request sender
55 */
56 protected String sender;
57
58 /**
59 * Check permission and parameters and handle request.
60 *
61 * @throws RequestHandlerForbiddenException if request is forbidden by preferences
62 * @throws RequestHandlerBadRequestException if request is invalid
63 * @throws RequestHandlerErrorException if an error occurs while processing request
64 */
65 public final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException {
66 checkMandatoryParams();
67 validateRequest();
68 checkPermission();
69 handleRequest();
70 }
71
72 /**
73 * Validates the request before attempting to perform it.
74 * @throws RequestHandlerBadRequestException if request is invalid
75 * @since 5678
76 */
77 protected abstract void validateRequest() throws RequestHandlerBadRequestException;
78
79 /**
80 * Handle a specific command sent as remote control.
81 *
82 * This method of the subclass will do the real work.
83 *
84 * @throws RequestHandlerErrorException if an error occurs while processing request
85 * @throws RequestHandlerBadRequestException if request is invalid
86 */
87 protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException;
88
89 /**
90 * Get a specific message to ask the user for permission for the operation
91 * requested via remote control.
92 *
93 * This message will be displayed to the user if the preference
94 * remotecontrol.always-confirm is true.
95 *
96 * @return the message
97 */
98 public abstract String getPermissionMessage();
99
100 /**
101 * Get a PermissionPref object containing the name of a special permission
102 * preference to individually allow the requested operation and an error
103 * message to be displayed when a disabled operation is requested.
104 *
105 * Default is not to check any special preference. Override this in a
106 * subclass to define permission preference and error message.
107 *
108 * @return the preference name and error message or null
109 */
110 public abstract PermissionPrefWithDefault getPermissionPref();
111
112 public abstract String[] getMandatoryParams();
113
114 public String[] getOptionalParams() {
115 return new String[0];
116 }
117
118 public String getUsage() {
119 return null;
120 }
121
122 public String[] getUsageExamples() {
123 return new String[0];
124 }
125
126 /**
127 * Returns usage examples for the given command. To be overriden only my handlers that define several commands.
128 * @param cmd The command asked
129 * @return Usage examples for the given command
130 * @since 6332
131 */
132 public String[] getUsageExamples(String cmd) {
133 return getUsageExamples();
134 }
135
136 /**
137 * Check permissions in preferences and display error message or ask for permission.
138 *
139 * @throws RequestHandlerForbiddenException if request is forbidden by preferences
140 */
141 public final void checkPermission() throws RequestHandlerForbiddenException {
142 /*
143 * If the subclass defines a specific preference and if this is set
144 * to false, abort with an error message.
145 *
146 * Note: we use the deprecated class here for compatibility with
147 * older versions of WMSPlugin.
148 */
149 PermissionPrefWithDefault permissionPref = getPermissionPref();
150 if (permissionPref != null && permissionPref.pref != null && !Config.getPref().getBoolean(permissionPref.pref, permissionPref.defaultVal)) {
151 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by preferences", myCommand);
152 Logging.info(err);
153 throw new RequestHandlerForbiddenException(err);
154 }
155
156 /* Does the user want to confirm everything?
157 * If yes, display specific confirmation message.
158 */
159 if (Config.getPref().getBoolean(globalConfirmationKey, globalConfirmationDefault)) {
160 // Ensure dialog box does not exceed main window size
161 Integer maxWidth = (int) Math.max(200, Main.parent.getWidth()*0.6);
162 String message = "<html><div>" + getPermissionMessage() +
163 "<br/>" + tr("Do you want to allow this?") + "</div></html>";
164 JLabel label = new JLabel(message);
165 if (label.getPreferredSize().width > maxWidth) {
166 label.setText(message.replaceFirst("<div>", "<div style=\"width:" + maxWidth + "px;\">"));
167 }
168 if (JOptionPane.showConfirmDialog(Main.parent, label,
169 tr("Confirm Remote Control action"),
170 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
171 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by user''s choice", myCommand);
172 throw new RequestHandlerForbiddenException(err);
173 }
174 }
175 }
176
177 /**
178 * Set request URL and parse args.
179 *
180 * @param url The request URL.
181 * @throws RequestHandlerBadRequestException if request URL is invalid
182 */
183 public void setUrl(String url) throws RequestHandlerBadRequestException {
184 this.request = url;
185 try {
186 parseArgs();
187 } catch (URISyntaxException e) {
188 throw new RequestHandlerBadRequestException(e);
189 }
190 }
191
192 /**
193 * Parse the request parameters as key=value pairs.
194 * The result will be stored in {@code this.args}.
195 *
196 * Can be overridden by subclass.
197 * @throws URISyntaxException if request URL is invalid
198 */
199 protected void parseArgs() throws URISyntaxException {
200 this.args = getRequestParameter(new URI(this.request));
201 }
202
203 /**
204 * Returns the request parameters.
205 * @param uri URI as string
206 * @return map of request parameters
207 * @see <a href="http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding">
208 * What every web developer must know about URL encoding</a>
209 */
210 static Map<String, String> getRequestParameter(URI uri) {
211 Map<String, String> r = new HashMap<>();
212 if (uri.getRawQuery() == null) {
213 return r;
214 }
215 for (String kv : uri.getRawQuery().split("&")) {
216 final String[] kvs = Utils.decodeUrl(kv).split("=", 2);
217 r.put(kvs[0], kvs.length > 1 ? kvs[1] : null);
218 }
219 return r;
220 }
221
222 void checkMandatoryParams() throws RequestHandlerBadRequestException {
223 String[] mandatory = getMandatoryParams();
224 String[] optional = getOptionalParams();
225 List<String> missingKeys = new LinkedList<>();
226 boolean error = false;
227 if (mandatory != null && args != null) {
228 for (String key : mandatory) {
229 String value = args.get(key);
230 if (value == null || value.isEmpty()) {
231 error = true;
232 Logging.warn('\'' + myCommand + "' remote control request must have '" + key + "' parameter");
233 missingKeys.add(key);
234 }
235 }
236 }
237 Set<String> knownParams = new HashSet<>();
238 if (mandatory != null)
239 Collections.addAll(knownParams, mandatory);
240 if (optional != null)
241 Collections.addAll(knownParams, optional);
242 if (args != null) {
243 for (String par: args.keySet()) {
244 if (!knownParams.contains(par)) {
245 Logging.warn("Unknown remote control parameter {0}, skipping it", par);
246 }
247 }
248 }
249 if (error) {
250 throw new RequestHandlerBadRequestException(
251 tr("The following keys are mandatory, but have not been provided: {0}",
252 Utils.join(", ", missingKeys)));
253 }
254 }
255
256 /**
257 * Save command associated with this handler.
258 *
259 * @param command The command.
260 */
261 public void setCommand(String command) {
262 if (command.charAt(0) == '/') {
263 command = command.substring(1);
264 }
265 myCommand = command;
266 }
267
268 public String getContent() {
269 return content;
270 }
271
272 public String getContentType() {
273 return contentType;
274 }
275
276 protected boolean isLoadInNewLayer() {
277 return args.get("new_layer") != null && !args.get("new_layer").isEmpty()
278 ? Boolean.parseBoolean(args.get("new_layer"))
279 : Config.getPref().getBoolean(loadInNewLayerKey, loadInNewLayerDefault);
280 }
281
282 public void setSender(String sender) {
283 this.sender = sender;
284 }
285
286 public static class RequestHandlerException extends Exception {
287
288 /**
289 * Constructs a new {@code RequestHandlerException}.
290 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
291 */
292 public RequestHandlerException(String message) {
293 super(message);
294 }
295
296 /**
297 * Constructs a new {@code RequestHandlerException}.
298 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
299 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
300 */
301 public RequestHandlerException(String message, Throwable cause) {
302 super(message, cause);
303 }
304
305 /**
306 * Constructs a new {@code RequestHandlerException}.
307 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
308 */
309 public RequestHandlerException(Throwable cause) {
310 super(cause);
311 }
312 }
313
314 public static class RequestHandlerErrorException extends RequestHandlerException {
315
316 /**
317 * Constructs a new {@code RequestHandlerErrorException}.
318 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
319 */
320 public RequestHandlerErrorException(Throwable cause) {
321 super(cause);
322 }
323 }
324
325 public static class RequestHandlerBadRequestException extends RequestHandlerException {
326
327 /**
328 * Constructs a new {@code RequestHandlerBadRequestException}.
329 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
330 */
331 public RequestHandlerBadRequestException(String message) {
332 super(message);
333 }
334
335 /**
336 * Constructs a new {@code RequestHandlerBadRequestException}.
337 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
338 */
339 public RequestHandlerBadRequestException(Throwable cause) {
340 super(cause);
341 }
342
343 /**
344 * Constructs a new {@code RequestHandlerBadRequestException}.
345 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
346 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
347 */
348 public RequestHandlerBadRequestException(String message, Throwable cause) {
349 super(message, cause);
350 }
351 }
352
353 public static class RequestHandlerForbiddenException extends RequestHandlerException {
354
355 /**
356 * Constructs a new {@code RequestHandlerForbiddenException}.
357 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
358 */
359 public RequestHandlerForbiddenException(String message) {
360 super(message);
361 }
362 }
363
364 public abstract static class RawURLParseRequestHandler extends RequestHandler {
365 @Override
366 protected void parseArgs() throws URISyntaxException {
367 Map<String, String> args = new HashMap<>();
368 if (request.indexOf('?') != -1) {
369 String query = request.substring(request.indexOf('?') + 1);
370 if (query.indexOf("url=") == 0) {
371 args.put("url", Utils.decodeUrl(query.substring(4)));
372 } else {
373 int urlIdx = query.indexOf("&url=");
374 if (urlIdx != -1) {
375 args.put("url", Utils.decodeUrl(query.substring(urlIdx + 5)));
376 query = query.substring(0, urlIdx);
377 } else if (query.indexOf('#') != -1) {
378 query = query.substring(0, query.indexOf('#'));
379 }
380 String[] params = query.split("&", -1);
381 for (String param : params) {
382 int eq = param.indexOf('=');
383 if (eq != -1) {
384 args.put(param.substring(0, eq), Utils.decodeUrl(param.substring(eq + 1)));
385 }
386 }
387 }
388 }
389 this.args = args;
390 }
391 }
392}
Note: See TracBrowser for help on using the repository browser.