source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java@ 10000

Last change on this file since 10000 was 10000, checked in by Don-vip, 8 years ago

sonar - fix various issues

  • Property svn:eol-style set to native
File size: 18.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol;
3
4import java.io.BufferedOutputStream;
5import java.io.BufferedReader;
6import java.io.IOException;
7import java.io.InputStreamReader;
8import java.io.OutputStream;
9import java.io.OutputStreamWriter;
10import java.io.PrintWriter;
11import java.io.StringWriter;
12import java.io.Writer;
13import java.net.Socket;
14import java.nio.charset.StandardCharsets;
15import java.util.Arrays;
16import java.util.Date;
17import java.util.HashMap;
18import java.util.Map;
19import java.util.Map.Entry;
20import java.util.StringTokenizer;
21import java.util.TreeMap;
22import java.util.regex.Matcher;
23import java.util.regex.Pattern;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.gui.help.HelpUtil;
27import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
28import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler;
29import org.openstreetmap.josm.io.remotecontrol.handler.FeaturesHandler;
30import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler;
31import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler;
32import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler;
33import org.openstreetmap.josm.io.remotecontrol.handler.LoadDataHandler;
34import org.openstreetmap.josm.io.remotecontrol.handler.LoadObjectHandler;
35import org.openstreetmap.josm.io.remotecontrol.handler.OpenFileHandler;
36import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
37import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException;
38import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerErrorException;
39import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException;
40import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
41import org.openstreetmap.josm.tools.Utils;
42
43/**
44 * Processes HTTP "remote control" requests.
45 */
46public class RequestProcessor extends Thread {
47 /**
48 * RemoteControl protocol version. Change minor number for compatible
49 * interface extensions. Change major number in case of incompatible
50 * changes.
51 */
52 public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " +
53 RemoteControl.protocolMajorVersion + ", \"minor\": " +
54 RemoteControl.protocolMinorVersion +
55 "}, \"application\": \"JOSM RemoteControl\"}";
56
57 /** The socket this processor listens on */
58 private final Socket request;
59
60 /**
61 * Collection of request handlers.
62 * Will be initialized with default handlers here. Other plug-ins
63 * can extend this list by using @see addRequestHandler
64 */
65 private static Map<String, Class<? extends RequestHandler>> handlers = new TreeMap<>();
66
67 /**
68 * Constructor
69 *
70 * @param request A socket to read the request.
71 */
72 public RequestProcessor(Socket request) {
73 super("RemoteControl request processor");
74 this.setDaemon(true);
75 this.request = request;
76 }
77
78 /**
79 * Spawns a new thread for the request
80 * @param request The request to process
81 */
82 public static void processRequest(Socket request) {
83 RequestProcessor processor = new RequestProcessor(request);
84 processor.start();
85 }
86
87 /**
88 * Add external request handler. Can be used by other plug-ins that
89 * want to use remote control.
90 *
91 * @param command The command to handle.
92 * @param handler The additional request handler.
93 */
94 public static void addRequestHandlerClass(String command, Class<? extends RequestHandler> handler) {
95 addRequestHandlerClass(command, handler, false);
96 }
97
98 /**
99 * Add external request handler. Message can be suppressed.
100 * (for internal use)
101 *
102 * @param command The command to handle.
103 * @param handler The additional request handler.
104 * @param silent Don't show message if true.
105 */
106 private static void addRequestHandlerClass(String command,
107 Class<? extends RequestHandler> handler, boolean silent) {
108 if (command.charAt(0) == '/') {
109 command = command.substring(1);
110 }
111 String commandWithSlash = '/' + command;
112 if (handlers.get(commandWithSlash) != null) {
113 Main.info("RemoteControl: ignoring duplicate command " + command
114 + " with handler " + handler.getName());
115 } else {
116 if (!silent) {
117 Main.info("RemoteControl: adding command \"" +
118 command + "\" (handled by " + handler.getSimpleName() + ')');
119 }
120 handlers.put(commandWithSlash, handler);
121 }
122 }
123
124 /** Add default request handlers */
125 static {
126 addRequestHandlerClass(LoadAndZoomHandler.command, LoadAndZoomHandler.class, true);
127 addRequestHandlerClass(LoadAndZoomHandler.command2, LoadAndZoomHandler.class, true);
128 addRequestHandlerClass(LoadDataHandler.command, LoadDataHandler.class, true);
129 addRequestHandlerClass(ImageryHandler.command, ImageryHandler.class, true);
130 addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
131 addRequestHandlerClass(AddWayHandler.command, AddWayHandler.class, true);
132 addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
133 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
134 addRequestHandlerClass(LoadObjectHandler.command, LoadObjectHandler.class, true);
135 addRequestHandlerClass(OpenFileHandler.command, OpenFileHandler.class, true);
136 addRequestHandlerClass(FeaturesHandler.command, FeaturesHandler.class, true);
137 }
138
139 /**
140 * The work is done here.
141 */
142 @Override
143 public void run() {
144 Writer out = null;
145 try {
146 OutputStream raw = new BufferedOutputStream(request.getOutputStream());
147 out = new OutputStreamWriter(raw, StandardCharsets.UTF_8);
148 BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), "ASCII"));
149
150 String get = in.readLine();
151 if (get == null) {
152 sendError(out);
153 return;
154 }
155 Main.info("RemoteControl received: " + get);
156
157 StringTokenizer st = new StringTokenizer(get);
158 if (!st.hasMoreTokens()) {
159 sendError(out);
160 return;
161 }
162 String method = st.nextToken();
163 if (!st.hasMoreTokens()) {
164 sendError(out);
165 return;
166 }
167 String url = st.nextToken();
168
169 if (!"GET".equals(method)) {
170 sendNotImplemented(out);
171 return;
172 }
173
174 int questionPos = url.indexOf('?');
175
176 String command = questionPos < 0 ? url : url.substring(0, questionPos);
177
178 Map<String, String> headers = new HashMap<>();
179 int k = 0, MAX_HEADERS = 20;
180 while (k < MAX_HEADERS) {
181 get = in.readLine();
182 if (get == null) break;
183 k++;
184 String[] h = get.split(": ", 2);
185 if (h.length == 2) {
186 headers.put(h[0], h[1]);
187 } else break;
188 }
189
190 // Who sent the request: trying our best to detect
191 // not from localhost => sender = IP
192 // from localhost: sender = referer header, if exists
193 String sender = null;
194
195 if (!request.getInetAddress().isLoopbackAddress()) {
196 sender = request.getInetAddress().getHostAddress();
197 } else {
198 String ref = headers.get("Referer");
199 Pattern r = Pattern.compile("(https?://)?([^/]*)");
200 if (ref != null) {
201 Matcher m = r.matcher(ref);
202 if (m.find()) {
203 sender = m.group(2);
204 }
205 }
206 if (sender == null) {
207 sender = "localhost";
208 }
209 }
210
211 // find a handler for this command
212 Class<? extends RequestHandler> handlerClass = handlers.get(command);
213 if (handlerClass == null) {
214 String usage = getUsageAsHtml();
215 String websiteDoc = HelpUtil.getWikiBaseHelpUrl() +"/Help/Preferences/RemoteControl";
216 String help = "No command specified! The following commands are available:<ul>" + usage
217 + "</ul>" + "See <a href=\""+websiteDoc+"\">"+websiteDoc+"</a> for complete documentation.";
218 sendBadRequest(out, help);
219 } else {
220 // create handler object
221 RequestHandler handler = handlerClass.newInstance();
222 try {
223 handler.setCommand(command);
224 handler.setUrl(url);
225 handler.setSender(sender);
226 handler.handle();
227 sendHeader(out, "200 OK", handler.getContentType(), false);
228 out.write("Content-length: " + handler.getContent().length()
229 + "\r\n");
230 out.write("\r\n");
231 out.write(handler.getContent());
232 out.flush();
233 } catch (RequestHandlerErrorException ex) {
234 sendError(out);
235 } catch (RequestHandlerBadRequestException ex) {
236 sendBadRequest(out, ex.getMessage());
237 } catch (RequestHandlerForbiddenException ex) {
238 sendForbidden(out, ex.getMessage());
239 }
240 }
241
242 } catch (IOException ioe) {
243 Main.debug(Main.getErrorMessage(ioe));
244 } catch (Exception e) {
245 Main.error(e);
246 try {
247 sendError(out);
248 } catch (IOException e1) {
249 Main.warn(e1);
250 }
251 } finally {
252 try {
253 request.close();
254 } catch (IOException e) {
255 Main.debug(Main.getErrorMessage(e));
256 }
257 }
258 }
259
260 /**
261 * Sends a 500 error: server error
262 *
263 * @param out
264 * The writer where the error is written
265 * @throws IOException
266 * If the error can not be written
267 */
268 private static void sendError(Writer out) throws IOException {
269 sendHeader(out, "500 Internal Server Error", "text/html", true);
270 out.write("<HTML>\r\n");
271 out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
272 out.write("</HEAD>\r\n");
273 out.write("<BODY>");
274 out.write("<H1>HTTP Error 500: Internal Server Error</H1>\r\n");
275 out.write("</BODY></HTML>\r\n");
276 out.flush();
277 }
278
279 /**
280 * Sends a 501 error: not implemented
281 *
282 * @param out
283 * The writer where the error is written
284 * @throws IOException
285 * If the error can not be written
286 */
287 private static void sendNotImplemented(Writer out) throws IOException {
288 sendHeader(out, "501 Not Implemented", "text/html", true);
289 out.write("<HTML>\r\n");
290 out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
291 out.write("</HEAD>\r\n");
292 out.write("<BODY>");
293 out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
294 out.write("</BODY></HTML>\r\n");
295 out.flush();
296 }
297
298 /**
299 * Sends a 403 error: forbidden
300 *
301 * @param out
302 * The writer where the error is written
303 * @param help
304 * Optional HTML help content to display, can be null
305 * @throws IOException
306 * If the error can not be written
307 */
308 private static void sendForbidden(Writer out, String help) throws IOException {
309 sendHeader(out, "403 Forbidden", "text/html", true);
310 out.write("<HTML>\r\n");
311 out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
312 out.write("</HEAD>\r\n");
313 out.write("<BODY>");
314 out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n");
315 if (help != null) {
316 out.write(help);
317 }
318 out.write("</BODY></HTML>\r\n");
319 out.flush();
320 }
321
322 /**
323 * Sends a 403 error: forbidden
324 *
325 * @param out
326 * The writer where the error is written
327 * @param help
328 * Optional HTML help content to display, can be null
329 * @throws IOException
330 * If the error can not be written
331 */
332 private static void sendBadRequest(Writer out, String help) throws IOException {
333 sendHeader(out, "400 Bad Request", "text/html", true);
334 out.write("<HTML>\r\n");
335 out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
336 out.write("</HEAD>\r\n");
337 out.write("<BODY>");
338 out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
339 if (help != null) {
340 out.write(help);
341 }
342 out.write("</BODY></HTML>\r\n");
343 out.flush();
344 }
345
346 /**
347 * Send common HTTP headers to the client.
348 *
349 * @param out
350 * The Writer
351 * @param status
352 * The status string ("200 OK", "500", etc)
353 * @param contentType
354 * The content type of the data sent
355 * @param endHeaders
356 * If true, adds a new line, ending the headers.
357 * @throws IOException
358 * When error
359 */
360 private static void sendHeader(Writer out, String status, String contentType,
361 boolean endHeaders) throws IOException {
362 out.write("HTTP/1.1 " + status + "\r\n");
363 Date now = new Date();
364 out.write("Date: " + now + "\r\n");
365 out.write("Server: JOSM RemoteControl\r\n");
366 out.write("Content-type: " + contentType + "\r\n");
367 out.write("Access-Control-Allow-Origin: *\r\n");
368 if (endHeaders)
369 out.write("\r\n");
370 }
371
372 public static String getHandlersInfoAsJSON() {
373 StringBuilder r = new StringBuilder();
374 boolean first = true;
375 r.append('[');
376
377 for (Entry<String, Class<? extends RequestHandler>> p : handlers.entrySet()) {
378 if (first) {
379 first = false;
380 } else {
381 r.append(", ");
382 }
383 r.append(getHandlerInfoAsJSON(p.getKey()));
384 }
385 r.append(']');
386
387 return r.toString();
388 }
389
390 public static String getHandlerInfoAsJSON(String cmd) {
391 try (StringWriter w = new StringWriter()) {
392 PrintWriter r = new PrintWriter(w);
393 RequestHandler handler = null;
394 try {
395 Class<?> c = handlers.get(cmd);
396 if (c == null) return null;
397 handler = handlers.get(cmd).newInstance();
398 } catch (InstantiationException | IllegalAccessException ex) {
399 Main.error(ex);
400 return null;
401 }
402
403 printJsonInfo(cmd, r, handler);
404 return w.toString();
405 } catch (IOException e) {
406 Main.error(e);
407 return null;
408 }
409 }
410
411 private static void printJsonInfo(String cmd, PrintWriter r, RequestHandler handler) {
412 r.printf("{ \"request\" : \"%s\"", cmd);
413 if (handler.getUsage() != null) {
414 r.printf(", \"usage\" : \"%s\"", handler.getUsage());
415 }
416 r.append(", \"parameters\" : [");
417
418 String[] params = handler.getMandatoryParams();
419 if (params != null) {
420 for (int i = 0; i < params.length; i++) {
421 if (i == 0) {
422 r.append('\"');
423 } else {
424 r.append(", \"");
425 }
426 r.append(params[i]).append('\"');
427 }
428 }
429 r.append("], \"optional\" : [");
430 String[] optional = handler.getOptionalParams();
431 if (optional != null) {
432 for (int i = 0; i < optional.length; i++) {
433 if (i == 0) {
434 r.append('\"');
435 } else {
436 r.append(", \"");
437 }
438 r.append(optional[i]).append('\"');
439 }
440 }
441
442 r.append("], \"examples\" : [");
443 String[] examples = handler.getUsageExamples(cmd.substring(1));
444 if (examples != null) {
445 for (int i = 0; i < examples.length; i++) {
446 if (i == 0) {
447 r.append('\"');
448 } else {
449 r.append(", \"");
450 }
451 r.append(examples[i]).append('\"');
452 }
453 }
454 r.append("]}");
455 }
456
457 /**
458 * Reports HTML message with the description of all available commands
459 * @return HTML message with the description of all available commands
460 * @throws IllegalAccessException if one handler class or its nullary constructor is not accessible.
461 * @throws InstantiationException if one handler class represents an abstract class, an interface, an array class,
462 * a primitive type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
463 */
464 public static String getUsageAsHtml() throws IllegalAccessException, InstantiationException {
465 StringBuilder usage = new StringBuilder(1024);
466 for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) {
467 RequestHandler sample = handler.getValue().newInstance();
468 String[] mandatory = sample.getMandatoryParams();
469 String[] optional = sample.getOptionalParams();
470 String[] examples = sample.getUsageExamples(handler.getKey().substring(1));
471 usage.append("<li>")
472 .append(handler.getKey());
473 if (sample.getUsage() != null && !sample.getUsage().isEmpty()) {
474 usage.append(" &mdash; <i>").append(sample.getUsage()).append("</i>");
475 }
476 if (mandatory != null) {
477 usage.append("<br/>mandatory parameters: ").append(Utils.join(", ", Arrays.asList(mandatory)));
478 }
479 if (optional != null) {
480 usage.append("<br/>optional parameters: ").append(Utils.join(", ", Arrays.asList(optional)));
481 }
482 if (examples != null) {
483 usage.append("<br/>examples: ");
484 for (String ex: examples) {
485 usage.append("<br/> <a href=\"http://localhost:8111").append(ex).append("\">").append(ex).append("</a>");
486 }
487 }
488 usage.append("</li>");
489 }
490 return usage.toString();
491 }
492}
Note: See TracBrowser for help on using the repository browser.