001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.io.remotecontrol; 003 004 import java.io.BufferedInputStream; 005 import java.io.BufferedOutputStream; 006 import java.io.IOException; 007 import java.io.InputStreamReader; 008 import java.io.OutputStream; 009 import java.io.OutputStreamWriter; 010 import java.io.Reader; 011 import java.io.Writer; 012 import java.net.Socket; 013 import java.util.Arrays; 014 import java.util.Date; 015 import java.util.Map; 016 import java.util.Map.Entry; 017 import java.util.StringTokenizer; 018 import java.util.TreeMap; 019 020 import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler; 021 import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler; 022 import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler; 023 import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler; 024 import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler; 025 import org.openstreetmap.josm.io.remotecontrol.handler.LoadObjectHandler; 026 import org.openstreetmap.josm.io.remotecontrol.handler.OpenFileHandler; 027 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler; 028 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException; 029 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerErrorException; 030 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException; 031 import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler; 032 import org.openstreetmap.josm.tools.Utils; 033 034 /** 035 * Processes HTTP "remote control" requests. 036 */ 037 public class RequestProcessor extends Thread { 038 /** 039 * RemoteControl protocol version. Change minor number for compatible 040 * interface extensions. Change major number in case of incompatible 041 * changes. 042 */ 043 public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " + 044 RemoteControl.protocolMajorVersion + ", \"minor\": " + 045 RemoteControl.protocolMinorVersion + 046 "}, \"application\": \"JOSM RemoteControl\"}"; 047 048 /** The socket this processor listens on */ 049 private Socket request; 050 051 /** 052 * Collection of request handlers. 053 * Will be initialized with default handlers here. Other plug-ins 054 * can extend this list by using @see addRequestHandler 055 */ 056 private static Map<String, Class<? extends RequestHandler>> handlers = new TreeMap<String, Class<? extends RequestHandler>>(); 057 058 /** 059 * Constructor 060 * 061 * @param request A socket to read the request. 062 */ 063 public RequestProcessor(Socket request) { 064 super("RemoteControl request processor"); 065 this.setDaemon(true); 066 this.request = request; 067 } 068 069 /** 070 * Spawns a new thread for the request 071 */ 072 public static void processRequest(Socket request) { 073 RequestProcessor processor = new RequestProcessor(request); 074 processor.start(); 075 } 076 077 /** 078 * Add external request handler. Can be used by other plug-ins that 079 * want to use remote control. 080 * 081 * @param command The command to handle. 082 * @param handler The additional request handler. 083 */ 084 static void addRequestHandlerClass(String command, 085 Class<? extends RequestHandler> handler) { 086 addRequestHandlerClass(command, handler, false); 087 } 088 089 /** 090 * Add external request handler. Message can be suppressed. 091 * (for internal use) 092 * 093 * @param command The command to handle. 094 * @param handler The additional request handler. 095 * @param silent Don't show message if true. 096 */ 097 private static void addRequestHandlerClass(String command, 098 Class<? extends RequestHandler> handler, boolean silent) { 099 if(command.charAt(0) == '/') 100 { 101 command = command.substring(1); 102 } 103 String commandWithSlash = "/" + command; 104 if (handlers.get(commandWithSlash) != null) { 105 System.out.println("RemoteControl: ignoring duplicate command " + command 106 + " with handler " + handler.getName()); 107 } else { 108 if (!silent) { 109 System.out.println("RemoteControl: adding command \"" + 110 command + "\" (handled by " + handler.getSimpleName() + ")"); 111 } 112 handlers.put(commandWithSlash, handler); 113 } 114 } 115 116 /** Add default request handlers */ 117 static { 118 addRequestHandlerClass(LoadAndZoomHandler.command, LoadAndZoomHandler.class, true); 119 addRequestHandlerClass(LoadAndZoomHandler.command2, LoadAndZoomHandler.class, true); 120 addRequestHandlerClass(ImageryHandler.command, ImageryHandler.class, true); 121 addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true); 122 addRequestHandlerClass(AddWayHandler.command, AddWayHandler.class, true); 123 addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true); 124 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true); 125 addRequestHandlerClass(LoadObjectHandler.command, LoadObjectHandler.class, true); 126 addRequestHandlerClass(OpenFileHandler.command, OpenFileHandler.class, true); 127 } 128 129 /** 130 * The work is done here. 131 */ 132 public void run() { 133 Writer out = null; 134 try { 135 OutputStream raw = new BufferedOutputStream( 136 request.getOutputStream()); 137 out = new OutputStreamWriter(raw); 138 Reader in = new InputStreamReader(new BufferedInputStream( 139 request.getInputStream()), "ASCII"); 140 141 StringBuffer requestLine = new StringBuffer(); 142 while (requestLine.length() < 1024 * 1024) { 143 int c = in.read(); 144 if (c == '\r' || c == '\n') 145 break; 146 requestLine.append((char) c); 147 } 148 149 System.out.println("RemoteControl received: " + requestLine); 150 String get = requestLine.toString(); 151 StringTokenizer st = new StringTokenizer(get); 152 if (!st.hasMoreTokens()) { 153 sendError(out); 154 return; 155 } 156 String method = st.nextToken(); 157 if (!st.hasMoreTokens()) { 158 sendError(out); 159 return; 160 } 161 String url = st.nextToken(); 162 163 if (!method.equals("GET")) { 164 sendNotImplemented(out); 165 return; 166 } 167 168 String command = null; 169 int questionPos = url.indexOf('?'); 170 if(questionPos < 0) 171 { 172 command = url; 173 } 174 else 175 { 176 command = url.substring(0, questionPos); 177 } 178 179 // find a handler for this command 180 Class<? extends RequestHandler> handlerClass = handlers.get(command); 181 if (handlerClass == null) { 182 // no handler found 183 StringBuilder usage = new StringBuilder(1024); 184 for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) { 185 String[] mandatory = handler.getValue().newInstance().getMandatoryParams(); 186 usage.append("<li>"); 187 usage.append(handler.getKey()); 188 if (mandatory != null) { 189 usage.append("<br/>mandatory parameter: ").append(Utils.join(", ", Arrays.asList(mandatory))); 190 } 191 usage.append("</li>"); 192 } 193 String help = "No command specified! The following commands are available:<ul>" 194 + usage.toString() 195 + "</ul>"; 196 sendBadRequest(out, help); 197 } else { 198 // create handler object 199 RequestHandler handler = handlerClass.newInstance(); 200 try { 201 handler.setCommand(command); 202 handler.setUrl(url); 203 handler.handle(); 204 sendHeader(out, "200 OK", handler.getContentType(), false); 205 out.write("Content-length: " + handler.getContent().length() 206 + "\r\n"); 207 out.write("\r\n"); 208 out.write(handler.getContent()); 209 out.flush(); 210 } catch (RequestHandlerErrorException ex) { 211 sendError(out); 212 } catch (RequestHandlerBadRequestException ex) { 213 sendBadRequest(out, ex.getMessage()); 214 } catch (RequestHandlerForbiddenException ex) { 215 sendForbidden(out, ex.getMessage()); 216 } 217 } 218 219 } catch (IOException ioe) { 220 } catch (Exception e) { 221 e.printStackTrace(); 222 try { 223 sendError(out); 224 } catch (IOException e1) { 225 } 226 } finally { 227 try { 228 request.close(); 229 } catch (IOException e) { 230 } 231 } 232 } 233 234 /** 235 * Sends a 500 error: server error 236 * 237 * @param out 238 * The writer where the error is written 239 * @throws IOException 240 * If the error can not be written 241 */ 242 private void sendError(Writer out) throws IOException { 243 sendHeader(out, "500 Internal Server Error", "text/html", true); 244 out.write("<HTML>\r\n"); 245 out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n"); 246 out.write("</HEAD>\r\n"); 247 out.write("<BODY>"); 248 out.write("<H1>HTTP Error 500: Internal Server Error</h2>\r\n"); 249 out.write("</BODY></HTML>\r\n"); 250 out.flush(); 251 } 252 253 /** 254 * Sends a 501 error: not implemented 255 * 256 * @param out 257 * The writer where the error is written 258 * @throws IOException 259 * If the error can not be written 260 */ 261 private void sendNotImplemented(Writer out) throws IOException { 262 sendHeader(out, "501 Not Implemented", "text/html", true); 263 out.write("<HTML>\r\n"); 264 out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n"); 265 out.write("</HEAD>\r\n"); 266 out.write("<BODY>"); 267 out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n"); 268 out.write("</BODY></HTML>\r\n"); 269 out.flush(); 270 } 271 272 /** 273 * Sends a 403 error: forbidden 274 * 275 * @param out 276 * The writer where the error is written 277 * @throws IOException 278 * If the error can not be written 279 */ 280 private void sendForbidden(Writer out, String help) throws IOException { 281 sendHeader(out, "403 Forbidden", "text/html", true); 282 out.write("<HTML>\r\n"); 283 out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n"); 284 out.write("</HEAD>\r\n"); 285 out.write("<BODY>"); 286 out.write("<H1>HTTP Error 403: Forbidden</h2>\r\n"); 287 if (help != null) { 288 out.write(help); 289 } 290 out.write("</BODY></HTML>\r\n"); 291 out.flush(); 292 } 293 294 /** 295 * Sends a 403 error: forbidden 296 * 297 * @param out 298 * The writer where the error is written 299 * @throws IOException 300 * If the error can not be written 301 */ 302 private void sendBadRequest(Writer out, String help) throws IOException { 303 sendHeader(out, "400 Bad Request", "text/html", true); 304 out.write("<HTML>\r\n"); 305 out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n"); 306 out.write("</HEAD>\r\n"); 307 out.write("<BODY>"); 308 out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n"); 309 if (help != null) { 310 out.write(help); 311 } 312 out.write("</BODY></HTML>\r\n"); 313 out.flush(); 314 } 315 316 /** 317 * Send common HTTP headers to the client. 318 * 319 * @param out 320 * The Writer 321 * @param status 322 * The status string ("200 OK", "500", etc) 323 * @param contentType 324 * The content type of the data sent 325 * @param endHeaders 326 * If true, adds a new line, ending the headers. 327 * @throws IOException 328 * When error 329 */ 330 private void sendHeader(Writer out, String status, String contentType, 331 boolean endHeaders) throws IOException { 332 out.write("HTTP/1.1 " + status + "\r\n"); 333 Date now = new Date(); 334 out.write("Date: " + now + "\r\n"); 335 out.write("Server: JOSM RemoteControl\r\n"); 336 out.write("Content-type: " + contentType + "\r\n"); 337 out.write("Access-Control-Allow-Origin: *\r\n"); 338 if (endHeaders) 339 out.write("\r\n"); 340 } 341 }