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 }