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

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

fix #10646 - Remote Control v1.6: new load_data handler to load OSM data directly from URL (modified patch by sanderd17)

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 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 static void addRequestHandlerClass(String command,
95 Class<? extends RequestHandler> handler) {
96 addRequestHandlerClass(command, handler, false);
97 }
98
99 /**
100 * Add external request handler. Message can be suppressed.
101 * (for internal use)
102 *
103 * @param command The command to handle.
104 * @param handler The additional request handler.
105 * @param silent Don't show message if true.
106 */
107 private static void addRequestHandlerClass(String command,
108 Class<? extends RequestHandler> handler, boolean silent) {
109 if(command.charAt(0) == '/') {
110 command = command.substring(1);
111 }
112 String commandWithSlash = "/" + command;
113 if (handlers.get(commandWithSlash) != null) {
114 Main.info("RemoteControl: ignoring duplicate command " + command
115 + " with handler " + handler.getName());
116 } else {
117 if (!silent) {
118 Main.info("RemoteControl: adding command \"" +
119 command + "\" (handled by " + handler.getSimpleName() + ")");
120 }
121 handlers.put(commandWithSlash, handler);
122 }
123 }
124
125 /** Add default request handlers */
126 static {
127 addRequestHandlerClass(LoadAndZoomHandler.command, LoadAndZoomHandler.class, true);
128 addRequestHandlerClass(LoadAndZoomHandler.command2, LoadAndZoomHandler.class, true);
129 addRequestHandlerClass(LoadDataHandler.command, LoadDataHandler.class, true);
130 addRequestHandlerClass(ImageryHandler.command, ImageryHandler.class, true);
131 addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
132 addRequestHandlerClass(AddWayHandler.command, AddWayHandler.class, true);
133 addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
134 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
135 addRequestHandlerClass(LoadObjectHandler.command, LoadObjectHandler.class, true);
136 addRequestHandlerClass(OpenFileHandler.command, OpenFileHandler.class, true);
137 addRequestHandlerClass(FeaturesHandler.command, FeaturesHandler.class, true);
138 }
139
140 /**
141 * The work is done here.
142 */
143 @Override
144 public void run() {
145 Writer out = null;
146 try {
147 OutputStream raw = new BufferedOutputStream(request.getOutputStream());
148 out = new OutputStreamWriter(raw, StandardCharsets.UTF_8);
149 BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), "ASCII"));
150
151 String get = in.readLine();
152 if (get == null) {
153 sendError(out);
154 return;
155 }
156 Main.info("RemoteControl received: " + get);
157
158 StringTokenizer st = new StringTokenizer(get);
159 if (!st.hasMoreTokens()) {
160 sendError(out);
161 return;
162 }
163 String method = st.nextToken();
164 if (!st.hasMoreTokens()) {
165 sendError(out);
166 return;
167 }
168 String url = st.nextToken();
169
170 if (!"GET".equals(method)) {
171 sendNotImplemented(out);
172 return;
173 }
174
175 int questionPos = url.indexOf('?');
176
177 String command = questionPos < 0 ? url : url.substring(0, questionPos);
178
179 Map <String,String> headers = new HashMap<>();
180 int k=0, MAX_HEADERS=20;
181 while (k<MAX_HEADERS) {
182 get=in.readLine();
183 if (get==null) break;
184 k++;
185 String[] h = get.split(": ", 2);
186 if (h.length==2) {
187 headers.put(h[0], h[1]);
188 } else break;
189 }
190
191 // Who sent the request: trying our best to detect
192 // not from localhost => sender = IP
193 // from localhost: sender = referer header, if exists
194 String sender = null;
195
196 if (!request.getInetAddress().isLoopbackAddress()) {
197 sender = request.getInetAddress().getHostAddress();
198 } else {
199 String ref = headers.get("Referer");
200 Pattern r = Pattern.compile("(https?://)?([^/]*)");
201 if (ref!=null) {
202 Matcher m = r.matcher(ref);
203 if (m.find()) {
204 sender = m.group(2);
205 }
206 }
207 if (sender == null) {
208 sender = "localhost";
209 }
210 }
211
212 // find a handler for this command
213 Class<? extends RequestHandler> handlerClass = handlers.get(command);
214 if (handlerClass == null) {
215 String usage = getUsageAsHtml();
216 String websiteDoc = HelpUtil.getWikiBaseHelpUrl() +"/Help/Preferences/RemoteControl";
217 String help = "No command specified! The following commands are available:<ul>" + usage
218 + "</ul>" + "See <a href=\""+websiteDoc+"\">"+websiteDoc+"</a> for complete documentation.";
219 sendBadRequest(out, help);
220 } else {
221 // create handler object
222 RequestHandler handler = handlerClass.newInstance();
223 try {
224 handler.setCommand(command);
225 handler.setUrl(url);
226 handler.setSender(sender);
227 handler.handle();
228 sendHeader(out, "200 OK", handler.getContentType(), false);
229 out.write("Content-length: " + handler.getContent().length()
230 + "\r\n");
231 out.write("\r\n");
232 out.write(handler.getContent());
233 out.flush();
234 } catch (RequestHandlerErrorException ex) {
235 sendError(out);
236 } catch (RequestHandlerBadRequestException ex) {
237 sendBadRequest(out, ex.getMessage());
238 } catch (RequestHandlerForbiddenException ex) {
239 sendForbidden(out, ex.getMessage());
240 }
241 }
242
243 } catch (IOException ioe) {
244 Main.debug(Main.getErrorMessage(ioe));
245 } catch (Exception e) {
246 Main.error(e);
247 try {
248 sendError(out);
249 } catch (IOException e1) {
250 Main.warn(e1);
251 }
252 } finally {
253 try {
254 request.close();
255 } catch (IOException e) {
256 Main.debug(Main.getErrorMessage(e));
257 }
258 }
259 }
260
261 /**
262 * Sends a 500 error: server error
263 *
264 * @param out
265 * The writer where the error is written
266 * @throws IOException
267 * If the error can not be written
268 */
269 private void sendError(Writer out) throws IOException {
270 sendHeader(out, "500 Internal Server Error", "text/html", true);
271 out.write("<HTML>\r\n");
272 out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
273 out.write("</HEAD>\r\n");
274 out.write("<BODY>");
275 out.write("<H1>HTTP Error 500: Internal Server Error</H1>\r\n");
276 out.write("</BODY></HTML>\r\n");
277 out.flush();
278 }
279
280 /**
281 * Sends a 501 error: not implemented
282 *
283 * @param out
284 * The writer where the error is written
285 * @throws IOException
286 * If the error can not be written
287 */
288 private void sendNotImplemented(Writer out) throws IOException {
289 sendHeader(out, "501 Not Implemented", "text/html", true);
290 out.write("<HTML>\r\n");
291 out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
292 out.write("</HEAD>\r\n");
293 out.write("<BODY>");
294 out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
295 out.write("</BODY></HTML>\r\n");
296 out.flush();
297 }
298
299 /**
300 * Sends a 403 error: forbidden
301 *
302 * @param out
303 * The writer where the error is written
304 * @throws IOException
305 * If the error can not be written
306 */
307 private void sendForbidden(Writer out, String help) throws IOException {
308 sendHeader(out, "403 Forbidden", "text/html", true);
309 out.write("<HTML>\r\n");
310 out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
311 out.write("</HEAD>\r\n");
312 out.write("<BODY>");
313 out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n");
314 if (help != null) {
315 out.write(help);
316 }
317 out.write("</BODY></HTML>\r\n");
318 out.flush();
319 }
320
321 /**
322 * Sends a 403 error: forbidden
323 *
324 * @param out
325 * The writer where the error is written
326 * @throws IOException
327 * If the error can not be written
328 */
329 private void sendBadRequest(Writer out, String help) throws IOException {
330 sendHeader(out, "400 Bad Request", "text/html", true);
331 out.write("<HTML>\r\n");
332 out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
333 out.write("</HEAD>\r\n");
334 out.write("<BODY>");
335 out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
336 if (help != null) {
337 out.write(help);
338 }
339 out.write("</BODY></HTML>\r\n");
340 out.flush();
341 }
342
343 /**
344 * Send common HTTP headers to the client.
345 *
346 * @param out
347 * The Writer
348 * @param status
349 * The status string ("200 OK", "500", etc)
350 * @param contentType
351 * The content type of the data sent
352 * @param endHeaders
353 * If true, adds a new line, ending the headers.
354 * @throws IOException
355 * When error
356 */
357 private void sendHeader(Writer out, String status, String contentType,
358 boolean endHeaders) throws IOException {
359 out.write("HTTP/1.1 " + status + "\r\n");
360 Date now = new Date();
361 out.write("Date: " + now + "\r\n");
362 out.write("Server: JOSM RemoteControl\r\n");
363 out.write("Content-type: " + contentType + "\r\n");
364 out.write("Access-Control-Allow-Origin: *\r\n");
365 if (endHeaders)
366 out.write("\r\n");
367 }
368
369 public static String getHandlersInfoAsJSON() {
370 StringBuilder r = new StringBuilder();
371 boolean first = true;
372 r.append("[");
373
374 for (Entry<String, Class<? extends RequestHandler>> p : handlers.entrySet()) {
375 if (first) {
376 first = false;
377 } else {
378 r.append(", ");
379 }
380 r.append(getHandlerInfoAsJSON(p.getKey()));
381 }
382 r.append("]");
383
384 return r.toString();
385 }
386
387 public static String getHandlerInfoAsJSON(String cmd) {
388 try (StringWriter w = new StringWriter()) {
389 PrintWriter r = new PrintWriter(w);
390 RequestHandler handler = null;
391 try {
392 Class<?> c = handlers.get(cmd);
393 if (c==null) return null;
394 handler = handlers.get(cmd).newInstance();
395 } catch (InstantiationException | IllegalAccessException ex) {
396 Main.error(ex);
397 return null;
398 }
399
400 printJsonInfo(cmd, r, handler);
401 return w.toString();
402 } catch (IOException e) {
403 Main.error(e);
404 return null;
405 }
406 }
407
408 private static void printJsonInfo(String cmd, PrintWriter r, RequestHandler handler) {
409 r.printf("{ \"request\" : \"%s\"", cmd);
410 if (handler.getUsage() != null) {
411 r.printf(", \"usage\" : \"%s\"", handler.getUsage());
412 }
413 r.append(", \"parameters\" : [");
414
415 String[] params = handler.getMandatoryParams();
416 if (params != null) {
417 for (int i = 0; i < params.length; i++) {
418 if (i == 0) {
419 r.append('\"');
420 } else {
421 r.append(", \"");
422 }
423 r.append(params[i]).append('\"');
424 }
425 }
426 r.append("], \"optional\" : [");
427 String[] optional = handler.getOptionalParams();
428 if (optional != null) {
429 for (int i = 0; i < optional.length; i++) {
430 if (i == 0) {
431 r.append('\"');
432 } else {
433 r.append(", \"");
434 }
435 r.append(optional[i]).append('\"');
436 }
437 }
438
439 r.append("], \"examples\" : [");
440 String[] examples = handler.getUsageExamples(cmd.substring(1));
441 if (examples != null) {
442 for (int i = 0; i < examples.length; i++) {
443 if (i == 0) {
444 r.append('\"');
445 } else {
446 r.append(", \"");
447 }
448 r.append(examples[i]).append('\"');
449 }
450 }
451 r.append("]}");
452 }
453
454 /**
455 * Reports HTML message with the description of all available commands
456 * @return HTML message with the description of all available commands
457 * @throws IllegalAccessException
458 * @throws InstantiationException
459 */
460 public static String getUsageAsHtml() throws IllegalAccessException, InstantiationException {
461 StringBuilder usage = new StringBuilder(1024);
462 for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) {
463 RequestHandler sample = handler.getValue().newInstance();
464 String[] mandatory = sample.getMandatoryParams();
465 String[] optional = sample.getOptionalParams();
466 String[] examples = sample.getUsageExamples(handler.getKey().substring(1));
467 usage.append("<li>");
468 usage.append(handler.getKey());
469 if (sample.getUsage() != null && !sample.getUsage().isEmpty()) {
470 usage.append(" &mdash; <i>").append(sample.getUsage()).append("</i>");
471 }
472 if (mandatory != null) {
473 usage.append("<br/>mandatory parameters: ").append(Utils.join(", ", Arrays.asList(mandatory)));
474 }
475 if (optional != null) {
476 usage.append("<br/>optional parameters: ").append(Utils.join(", ", Arrays.asList(optional)));
477 }
478 if (examples != null) {
479 usage.append("<br/>examples: ");
480 for (String ex: examples) {
481 usage.append("<br/> <a href=\"http://localhost:8111"+ex+"\">"+ex+"</a>");
482 }
483 }
484 usage.append("</li>");
485 }
486 return usage.toString();
487 }
488}
Note: See TracBrowser for help on using the repository browser.