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

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

Sonar - fix various violations

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