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    }