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

Last change on this file since 5876 was 5876, checked in by akks, 11 years ago

Remote control: allow adding tags without confirmation for current session (add_tags), see #8612
added parsing of request headers and detecting request sender by IP and "referer" HTTP header

  • Property svn:eol-style set to native
File size: 9.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.io.UnsupportedEncodingException;
7import java.net.URLDecoder;
8import java.text.MessageFormat;
9import java.util.HashMap;
10import java.util.LinkedList;
11import java.util.List;
12
13import javax.swing.JLabel;
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
18import org.openstreetmap.josm.tools.Utils;
19
20/**
21 * This is the parent of all classes that handle a specific remote control command
22 *
23 * @author Bodo Meissner
24 */
25public abstract class RequestHandler {
26
27 public static final String globalConfirmationKey = "remotecontrol.always-confirm";
28 public static final boolean globalConfirmationDefault = false;
29 public static final String loadInNewLayerKey = "remotecontrol.new-layer";
30 public static final boolean loadInNewLayerDefault = false;
31
32 /** The GET request arguments */
33 protected HashMap<String,String> args;
34
35 /** The request URL without "GET". */
36 protected String request;
37
38 /** default response */
39 protected String content = "OK\r\n";
40 /** default content type */
41 protected String contentType = "text/plain";
42
43 /** will be filled with the command assigned to the subclass */
44 protected String myCommand;
45
46 /**
47 * who send th request?
48 * the host from refrerer header or IP of request sender
49 */
50 protected String sender;
51
52 /**
53 * Check permission and parameters and handle request.
54 *
55 * @throws RequestHandlerForbiddenException
56 * @throws RequestHandlerBadRequestException
57 * @throws RequestHandlerErrorException
58 */
59 public final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException
60 {
61 checkMandatoryParams();
62 validateRequest();
63 checkPermission();
64 handleRequest();
65 }
66
67 /**
68 * Validates the request before attempting to perform it.
69 * @throws RequestHandlerBadRequestException
70 * @since 5678
71 */
72 protected abstract void validateRequest() throws RequestHandlerBadRequestException;
73
74 /**
75 * Handle a specific command sent as remote control.
76 *
77 * This method of the subclass will do the real work.
78 *
79 * @throws RequestHandlerErrorException
80 * @throws RequestHandlerBadRequestException
81 */
82 protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException;
83
84 /**
85 * Get a specific message to ask the user for permission for the operation
86 * requested via remote control.
87 *
88 * This message will be displayed to the user if the preference
89 * remotecontrol.always-confirm is true.
90 *
91 * @return the message
92 */
93 abstract public String getPermissionMessage();
94
95 /**
96 * Get a PermissionPref object containing the name of a special permission
97 * preference to individually allow the requested operation and an error
98 * message to be displayed when a disabled operation is requested.
99 *
100 * Default is not to check any special preference. Override this in a
101 * subclass to define permission preference and error message.
102 *
103 * @return the preference name and error message or null
104 */
105 abstract public PermissionPrefWithDefault getPermissionPref();
106
107 abstract public String[] getMandatoryParams();
108
109 /**
110 * Check permissions in preferences and display error message
111 * or ask for permission.
112 *
113 * @throws RequestHandlerForbiddenException
114 */
115 final public void checkPermission() throws RequestHandlerForbiddenException
116 {
117 /*
118 * If the subclass defines a specific preference and if this is set
119 * to false, abort with an error message.
120 *
121 * Note: we use the deprecated class here for compatibility with
122 * older versions of WMSPlugin.
123 */
124 PermissionPrefWithDefault permissionPref = getPermissionPref();
125 if((permissionPref != null) && (permissionPref.pref != null))
126 {
127 if (!Main.pref.getBoolean(permissionPref.pref, permissionPref.defaultVal)) {
128 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by preferences", myCommand);
129 System.out.println(err);
130 throw new RequestHandlerForbiddenException(err);
131 }
132 }
133
134 /* Does the user want to confirm everything?
135 * If yes, display specific confirmation message.
136 */
137 if (Main.pref.getBoolean(globalConfirmationKey, globalConfirmationDefault)) {
138 // Ensure dialog box does not exceed main window size
139 Integer maxWidth = (int) Math.max(200, Main.parent.getWidth()*0.6);
140 String message = "<html><div>" + getPermissionMessage() +
141 "<br/>" + tr("Do you want to allow this?") + "</div></html>";
142 JLabel label = new JLabel(message);
143 if (label.getPreferredSize().width > maxWidth) {
144 label.setText(message.replaceFirst("<div>", "<div style=\"width:" + maxWidth + "px;\">"));
145 }
146 if (JOptionPane.showConfirmDialog(Main.parent, label,
147 tr("Confirm Remote Control action"),
148 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
149 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by user''s choice", myCommand);
150 throw new RequestHandlerForbiddenException(err);
151 }
152 }
153 }
154
155 /**
156 * Set request URL and parse args.
157 *
158 * @param url The request URL.
159 */
160 public void setUrl(String url) {
161 this.request = url;
162 parseArgs();
163 }
164
165 /**
166 * Parse the request parameters as key=value pairs.
167 * The result will be stored in {@code this.args}.
168 *
169 * Can be overridden by subclass.
170 */
171 protected void parseArgs() {
172 try {
173 String req = URLDecoder.decode(this.request, "UTF-8");
174 HashMap<String, String> args = new HashMap<String, String>();
175 if (req.indexOf('?') != -1) {
176 String query = req.substring(req.indexOf('?') + 1);
177 if (query.indexOf('#') != -1) {
178 query = query.substring(0, query.indexOf('#'));
179 }
180 String[] params = query.split("&", -1);
181 for (String param : params) {
182 int eq = param.indexOf('=');
183 if (eq != -1) {
184 args.put(param.substring(0, eq), param.substring(eq + 1));
185 }
186 }
187 }
188 this.args = args;
189 } catch (UnsupportedEncodingException ex) {
190 throw new IllegalStateException(ex);
191 }
192 }
193
194 void checkMandatoryParams() throws RequestHandlerBadRequestException {
195 String[] mandatory = getMandatoryParams();
196 if(mandatory == null) return;
197
198 List<String> missingKeys = new LinkedList<String>();
199 boolean error = false;
200 for (String key : mandatory) {
201 String value = args.get(key);
202 if ((value == null) || (value.length() == 0)) {
203 error = true;
204 System.out.println("'" + myCommand + "' remote control request must have '" + key + "' parameter");
205 missingKeys.add(key);
206 }
207 }
208 if (error) {
209 throw new RequestHandlerBadRequestException(
210 "The following keys are mandatory, but have not been provided: "
211 + Utils.join(", ", missingKeys));
212 }
213 }
214
215 /**
216 * Save command associated with this handler.
217 *
218 * @param command The command.
219 */
220 public void setCommand(String command)
221 {
222 if (command.charAt(0) == '/') {
223 command = command.substring(1);
224 }
225 myCommand = command;
226 }
227
228 public String getContent() {
229 return content;
230 }
231
232 public String getContentType() {
233 return contentType;
234 }
235
236 protected boolean isLoadInNewLayer() {
237 return args.get("new_layer") != null && !args.get("new_layer").isEmpty()
238 ? Boolean.parseBoolean(args.get("new_layer"))
239 : Main.pref.getBoolean(loadInNewLayerKey, loadInNewLayerDefault);
240 }
241
242 protected final String decodeParam(String param) {
243 try {
244 return URLDecoder.decode(param, "UTF-8");
245 } catch (UnsupportedEncodingException e) {
246 throw new RuntimeException();
247 }
248 }
249
250 public void setSender(String sender) {
251 this.sender = sender;
252 }
253
254 public static class RequestHandlerException extends Exception {
255
256 public RequestHandlerException(String message) {
257 super(message);
258 }
259
260 public RequestHandlerException() {
261 }
262 }
263
264 public static class RequestHandlerErrorException extends RequestHandlerException {
265 }
266
267 public static class RequestHandlerBadRequestException extends RequestHandlerException {
268
269 public RequestHandlerBadRequestException(String message) {
270 super(message);
271 }
272 }
273
274 public static class RequestHandlerForbiddenException extends RequestHandlerException {
275 private static final long serialVersionUID = 2263904699747115423L;
276
277 public RequestHandlerForbiddenException(String message) {
278 super(message);
279 }
280 }
281}
Note: See TracBrowser for help on using the repository browser.