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

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

see #8465 - replace Utils.UTF_8 by StandardCharsets.UTF_8, new in Java 7

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