001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.io.remotecontrol.handler; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.io.UnsupportedEncodingException; 007 import java.net.URLDecoder; 008 import java.text.MessageFormat; 009 import java.util.HashMap; 010 import java.util.LinkedList; 011 import java.util.List; 012 013 import javax.swing.JOptionPane; 014 015 import org.openstreetmap.josm.Main; 016 import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault; 017 import org.openstreetmap.josm.tools.Utils; 018 019 /** 020 * This is the parent of all classes that handle a specific remote control command 021 * 022 * @author Bodo Meissner 023 */ 024 public abstract class RequestHandler { 025 026 public static final String globalConfirmationKey = "remotecontrol.always-confirm"; 027 public static final boolean globalConfirmationDefault = false; 028 public static final String loadInNewLayerKey = "remotecontrol.new-layer"; 029 public static final boolean loadInNewLayerDefault = false; 030 031 /** The GET request arguments */ 032 protected HashMap<String,String> args; 033 034 /** The request URL without "GET". */ 035 protected String request; 036 037 /** default response */ 038 protected String content = "OK\r\n"; 039 /** default content type */ 040 protected String contentType = "text/plain"; 041 042 /** will be filled with the command assigned to the subclass */ 043 protected String myCommand; 044 045 /** 046 * Check permission and parameters and handle request. 047 * 048 * @throws RequestHandlerForbiddenException 049 * @throws RequestHandlerBadRequestException 050 * @throws RequestHandlerErrorException 051 */ 052 public final void handle() throws RequestHandlerForbiddenException, RequestHandlerBadRequestException, RequestHandlerErrorException 053 { 054 checkMandatoryParams(); 055 checkPermission(); 056 handleRequest(); 057 } 058 059 /** 060 * Handle a specific command sent as remote control. 061 * 062 * This method of the subclass will do the real work. 063 * 064 * @throws RequestHandlerErrorException 065 * @throws RequestHandlerBadRequestException 066 */ 067 protected abstract void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException; 068 069 /** 070 * Get a specific message to ask the user for permission for the operation 071 * requested via remote control. 072 * 073 * This message will be displayed to the user if the preference 074 * remotecontrol.always-confirm is true. 075 * 076 * @return the message 077 */ 078 abstract public String getPermissionMessage(); 079 080 /** 081 * Get a PermissionPref object containing the name of a special permission 082 * preference to individually allow the requested operation and an error 083 * message to be displayed when a disabled operation is requested. 084 * 085 * Default is not to check any special preference. Override this in a 086 * subclass to define permission preference and error message. 087 * 088 * @return the preference name and error message or null 089 */ 090 abstract public PermissionPrefWithDefault getPermissionPref(); 091 092 abstract public String[] getMandatoryParams(); 093 094 /** 095 * Check permissions in preferences and display error message 096 * or ask for permission. 097 * 098 * @throws RequestHandlerForbiddenException 099 */ 100 final public void checkPermission() throws RequestHandlerForbiddenException 101 { 102 /* 103 * If the subclass defines a specific preference and if this is set 104 * to false, abort with an error message. 105 * 106 * Note: we use the deprecated class here for compatibility with 107 * older versions of WMSPlugin. 108 */ 109 PermissionPrefWithDefault permissionPref = getPermissionPref(); 110 if((permissionPref != null) && (permissionPref.pref != null)) 111 { 112 if (!Main.pref.getBoolean(permissionPref.pref, permissionPref.defaultVal)) { 113 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by preferences", myCommand); 114 System.out.println(err); 115 throw new RequestHandlerForbiddenException(err); 116 } 117 } 118 119 /* Does the user want to confirm everything? 120 * If yes, display specific confirmation message. 121 */ 122 if (Main.pref.getBoolean(globalConfirmationKey, globalConfirmationDefault)) { 123 if (JOptionPane.showConfirmDialog(Main.parent, 124 "<html>" + getPermissionMessage() + 125 "<br>" + tr("Do you want to allow this?"), 126 tr("Confirm Remote Control action"), 127 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { 128 String err = MessageFormat.format("RemoteControl: ''{0}'' forbidden by user''s choice", myCommand); 129 throw new RequestHandlerForbiddenException(err); 130 } 131 } 132 } 133 134 /** 135 * Set request URL and parse args. 136 * 137 * @param url The request URL. 138 */ 139 public void setUrl(String url) { 140 this.request = url; 141 parseArgs(); 142 } 143 144 /** 145 * Parse the request parameters as key=value pairs. 146 * The result will be stored in {@code this.args}. 147 * 148 * Can be overridden by subclass. 149 */ 150 protected void parseArgs() { 151 try { 152 String req = URLDecoder.decode(this.request, "UTF-8"); 153 HashMap<String, String> args = new HashMap<String, String>(); 154 if (req.indexOf('?') != -1) { 155 String query = req.substring(req.indexOf('?') + 1); 156 if (query.indexOf('#') != -1) { 157 query = query.substring(0, query.indexOf('#')); 158 } 159 String[] params = query.split("&", -1); 160 for (String param : params) { 161 int eq = param.indexOf('='); 162 if (eq != -1) { 163 args.put(param.substring(0, eq), param.substring(eq + 1)); 164 } 165 } 166 } 167 this.args = args; 168 } catch (UnsupportedEncodingException ex) { 169 throw new IllegalStateException(ex); 170 } 171 } 172 173 void checkMandatoryParams() throws RequestHandlerBadRequestException { 174 String[] mandatory = getMandatoryParams(); 175 if(mandatory == null) return; 176 177 List<String> missingKeys = new LinkedList<String>(); 178 boolean error = false; 179 for (String key : mandatory) { 180 String value = args.get(key); 181 if ((value == null) || (value.length() == 0)) { 182 error = true; 183 System.out.println("'" + myCommand + "' remote control request must have '" + key + "' parameter"); 184 missingKeys.add(key); 185 } 186 } 187 if (error) { 188 throw new RequestHandlerBadRequestException( 189 "The following keys are mandatory, but have not been provided: " 190 + Utils.join(", ", missingKeys)); 191 } 192 } 193 194 /** 195 * Save command associated with this handler. 196 * 197 * @param command The command. 198 */ 199 public void setCommand(String command) 200 { 201 if (command.charAt(0) == '/') { 202 command = command.substring(1); 203 } 204 myCommand = command; 205 } 206 207 public String getContent() { 208 return content; 209 } 210 211 public String getContentType() { 212 return contentType; 213 } 214 215 protected boolean isLoadInNewLayer() { 216 return args.get("new_layer") != null && !args.get("new_layer").isEmpty() 217 ? Boolean.parseBoolean(args.get("new_layer")) 218 : Main.pref.getBoolean(loadInNewLayerKey, loadInNewLayerDefault); 219 } 220 221 public static class RequestHandlerException extends Exception { 222 223 public RequestHandlerException(String message) { 224 super(message); 225 } 226 227 public RequestHandlerException() { 228 } 229 } 230 231 public static class RequestHandlerErrorException extends RequestHandlerException { 232 } 233 234 public static class RequestHandlerBadRequestException extends RequestHandlerException { 235 236 public RequestHandlerBadRequestException(String message) { 237 super(message); 238 } 239 } 240 241 public static class RequestHandlerForbiddenException extends RequestHandlerException { 242 private static final long serialVersionUID = 2263904699747115423L; 243 244 public RequestHandlerForbiddenException(String message) { 245 super(message); 246 } 247 } 248 }