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

Last change on this file since 5876 was 5876, checked in by akks, 11 years ago

Remote control: allow adding tags without confirmation for current session (add_tags), see #8612
added parsing of request headers and detecting request sender by IP and "referer" HTTP header

  • Property svn:eol-style set to native
File size: 13.5 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.Writer;
11import java.net.Socket;
12import java.util.Arrays;
13import java.util.Date;
14import java.util.HashMap;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.StringTokenizer;
18import java.util.TreeMap;
19import java.util.regex.Matcher;
20import java.util.regex.Pattern;
21
22import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
23import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler;
24import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler;
25import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler;
26import org.openstreetmap.josm.io.remotecontrol.handler.LoadAndZoomHandler;
27import org.openstreetmap.josm.io.remotecontrol.handler.LoadObjectHandler;
28import org.openstreetmap.josm.io.remotecontrol.handler.OpenFileHandler;
29import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
30import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerBadRequestException;
31import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerErrorException;
32import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException;
33import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * Processes HTTP "remote control" requests.
38 */
39public class RequestProcessor extends Thread {
40 /**
41 * RemoteControl protocol version. Change minor number for compatible
42 * interface extensions. Change major number in case of incompatible
43 * changes.
44 */
45 public static final String PROTOCOLVERSION = "{\"protocolversion\": {\"major\": " +
46 RemoteControl.protocolMajorVersion + ", \"minor\": " +
47 RemoteControl.protocolMinorVersion +
48 "}, \"application\": \"JOSM RemoteControl\"}";
49
50 /** The socket this processor listens on */
51 private Socket request;
52
53 /**
54 * Collection of request handlers.
55 * Will be initialized with default handlers here. Other plug-ins
56 * can extend this list by using @see addRequestHandler
57 */
58 private static Map<String, Class<? extends RequestHandler>> handlers = new TreeMap<String, Class<? extends RequestHandler>>();
59
60 /**
61 * Constructor
62 *
63 * @param request A socket to read the request.
64 */
65 public RequestProcessor(Socket request) {
66 super("RemoteControl request processor");
67 this.setDaemon(true);
68 this.request = request;
69 }
70
71 /**
72 * Spawns a new thread for the request
73 */
74 public static void processRequest(Socket request) {
75 RequestProcessor processor = new RequestProcessor(request);
76 processor.start();
77 }
78
79 /**
80 * Add external request handler. Can be used by other plug-ins that
81 * want to use remote control.
82 *
83 * @param command The command to handle.
84 * @param handler The additional request handler.
85 */
86 static void addRequestHandlerClass(String command,
87 Class<? extends RequestHandler> handler) {
88 addRequestHandlerClass(command, handler, false);
89 }
90
91 /**
92 * Add external request handler. Message can be suppressed.
93 * (for internal use)
94 *
95 * @param command The command to handle.
96 * @param handler The additional request handler.
97 * @param silent Don't show message if true.
98 */
99 private static void addRequestHandlerClass(String command,
100 Class<? extends RequestHandler> handler, boolean silent) {
101 if(command.charAt(0) == '/')
102 {
103 command = command.substring(1);
104 }
105 String commandWithSlash = "/" + command;
106 if (handlers.get(commandWithSlash) != null) {
107 System.out.println("RemoteControl: ignoring duplicate command " + command
108 + " with handler " + handler.getName());
109 } else {
110 if (!silent) {
111 System.out.println("RemoteControl: adding command \"" +
112 command + "\" (handled by " + handler.getSimpleName() + ")");
113 }
114 handlers.put(commandWithSlash, handler);
115 }
116 }
117
118 /** Add default request handlers */
119 static {
120 addRequestHandlerClass(LoadAndZoomHandler.command, LoadAndZoomHandler.class, true);
121 addRequestHandlerClass(LoadAndZoomHandler.command2, LoadAndZoomHandler.class, true);
122 addRequestHandlerClass(ImageryHandler.command, ImageryHandler.class, true);
123 addRequestHandlerClass(AddNodeHandler.command, AddNodeHandler.class, true);
124 addRequestHandlerClass(AddWayHandler.command, AddWayHandler.class, true);
125 addRequestHandlerClass(ImportHandler.command, ImportHandler.class, true);
126 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true);
127 addRequestHandlerClass(LoadObjectHandler.command, LoadObjectHandler.class, true);
128 addRequestHandlerClass(OpenFileHandler.command, OpenFileHandler.class, true);
129 }
130
131 /**
132 * The work is done here.
133 */
134 public void run() {
135 Writer out = null;
136 try {
137 OutputStream raw = new BufferedOutputStream(
138 request.getOutputStream());
139 out = new OutputStreamWriter(raw);
140 BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), "ASCII"));
141
142 String get = in.readLine();
143 System.out.println("RemoteControl received: " + get);
144
145 StringTokenizer st = new StringTokenizer(get);
146 if (!st.hasMoreTokens()) {
147 sendError(out);
148 return;
149 }
150 String method = st.nextToken();
151 if (!st.hasMoreTokens()) {
152 sendError(out);
153 return;
154 }
155 String url = st.nextToken();
156
157 if (!method.equals("GET")) {
158 sendNotImplemented(out);
159 return;
160 }
161
162 int questionPos = url.indexOf('?');
163
164 String command = questionPos < 0 ? url : url.substring(0, questionPos);
165
166 Map <String,String> headers = new HashMap<String, String>();
167 int k=0, MAX_HEADERS=20;
168 while (k<MAX_HEADERS) {
169 get=in.readLine();
170 if (get==null) break;
171 k++;
172 String h[] = get.split(": ", 2);
173 if (h.length==2) {
174 headers.put(h[0], h[1]);
175 } else break;
176 }
177
178 // Who sent the request: trying our best to detect
179 // not from localhost => sender = IP
180 // from localhost: sender = referer header, if exists
181 String sender = null;
182
183 if (!request.getInetAddress().isLoopbackAddress()) {
184 sender = request.getInetAddress().getHostAddress();
185 } else {
186 String ref = headers.get("Referer");
187 Pattern r = Pattern.compile("(https?://)?([^/]*)");
188 if (ref!=null) {
189 Matcher m = r.matcher(ref);
190 if (m.find()) {
191 sender = m.group(2);
192 }
193 }
194 if (sender == null) {
195 sender = "localhost";
196 }
197 }
198
199 // find a handler for this command
200 Class<? extends RequestHandler> handlerClass = handlers.get(command);
201 if (handlerClass == null) {
202 // no handler found
203 StringBuilder usage = new StringBuilder(1024);
204 for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) {
205 String[] mandatory = handler.getValue().newInstance().getMandatoryParams();
206 usage.append("<li>");
207 usage.append(handler.getKey());
208 if (mandatory != null) {
209 usage.append("<br/>mandatory parameter: ").append(Utils.join(", ", Arrays.asList(mandatory)));
210 }
211 usage.append("</li>");
212 }
213 String websiteDoc = "http://josm.openstreetmap.de/wiki/Help/Preferences/RemoteControl";
214 String help = "No command specified! The following commands are available:<ul>"
215 + usage.toString()
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 } catch (Exception e) {
243 e.printStackTrace();
244 try {
245 sendError(out);
246 } catch (IOException e1) {
247 }
248 } finally {
249 try {
250 request.close();
251 } catch (IOException e) {
252 }
253 }
254 }
255
256 /**
257 * Sends a 500 error: server error
258 *
259 * @param out
260 * The writer where the error is written
261 * @throws IOException
262 * If the error can not be written
263 */
264 private void sendError(Writer out) throws IOException {
265 sendHeader(out, "500 Internal Server Error", "text/html", true);
266 out.write("<HTML>\r\n");
267 out.write("<HEAD><TITLE>Internal Error</TITLE>\r\n");
268 out.write("</HEAD>\r\n");
269 out.write("<BODY>");
270 out.write("<H1>HTTP Error 500: Internal Server Error</h2>\r\n");
271 out.write("</BODY></HTML>\r\n");
272 out.flush();
273 }
274
275 /**
276 * Sends a 501 error: not implemented
277 *
278 * @param out
279 * The writer where the error is written
280 * @throws IOException
281 * If the error can not be written
282 */
283 private void sendNotImplemented(Writer out) throws IOException {
284 sendHeader(out, "501 Not Implemented", "text/html", true);
285 out.write("<HTML>\r\n");
286 out.write("<HEAD><TITLE>Not Implemented</TITLE>\r\n");
287 out.write("</HEAD>\r\n");
288 out.write("<BODY>");
289 out.write("<H1>HTTP Error 501: Not Implemented</h2>\r\n");
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 sendForbidden(Writer out, String help) throws IOException {
303 sendHeader(out, "403 Forbidden", "text/html", true);
304 out.write("<HTML>\r\n");
305 out.write("<HEAD><TITLE>Forbidden</TITLE>\r\n");
306 out.write("</HEAD>\r\n");
307 out.write("<BODY>");
308 out.write("<H1>HTTP Error 403: Forbidden</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 * Sends a 403 error: forbidden
318 *
319 * @param out
320 * The writer where the error is written
321 * @throws IOException
322 * If the error can not be written
323 */
324 private void sendBadRequest(Writer out, String help) throws IOException {
325 sendHeader(out, "400 Bad Request", "text/html", true);
326 out.write("<HTML>\r\n");
327 out.write("<HEAD><TITLE>Bad Request</TITLE>\r\n");
328 out.write("</HEAD>\r\n");
329 out.write("<BODY>");
330 out.write("<H1>HTTP Error 400: Bad Request</h2>\r\n");
331 if (help != null) {
332 out.write(help);
333 }
334 out.write("</BODY></HTML>\r\n");
335 out.flush();
336 }
337
338 /**
339 * Send common HTTP headers to the client.
340 *
341 * @param out
342 * The Writer
343 * @param status
344 * The status string ("200 OK", "500", etc)
345 * @param contentType
346 * The content type of the data sent
347 * @param endHeaders
348 * If true, adds a new line, ending the headers.
349 * @throws IOException
350 * When error
351 */
352 private void sendHeader(Writer out, String status, String contentType,
353 boolean endHeaders) throws IOException {
354 out.write("HTTP/1.1 " + status + "\r\n");
355 Date now = new Date();
356 out.write("Date: " + now + "\r\n");
357 out.write("Server: JOSM RemoteControl\r\n");
358 out.write("Content-type: " + contentType + "\r\n");
359 out.write("Access-Control-Allow-Origin: *\r\n");
360 if (endHeaders)
361 out.write("\r\n");
362 }
363}
Note: See TracBrowser for help on using the repository browser.