001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.lang.reflect.InvocationTargetException; 005import java.lang.reflect.Method; 006import java.nio.charset.StandardCharsets; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013import java.util.prefs.Preferences; 014 015/** 016 * Utility class to access Window registry (read access only). 017 * As the implementation relies on internal JDK class {@code java.util.prefs.WindowsPreferences} and its native JNI 018 * method {@code Java_java_util_prefs_WindowsPreferences_WindowsRegQueryValueEx}, only String values (REG_SZ) 019 * are supported. 020 * Adapted from <a href="http://stackoverflow.com/a/6163701/2257172">StackOverflow</a>. 021 * @since 12217 022 */ 023public final class WinRegistry { 024 025 /** 026 * Registry entries subordinate to this key define the preferences of the current user. 027 * These preferences include the settings of environment variables, data about program groups, 028 * colors, printers, network connections, and application preferences. 029 * See <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx">Predefined Keys</a> 030 */ 031 public static final int HKEY_CURRENT_USER = 0x80000001; 032 033 /** 034 * Registry entries subordinate to this key define the physical state of the computer, including data about the bus type, 035 * system memory, and installed hardware and software. It contains subkeys that hold current configuration data, 036 * including Plug and Play information (the Enum branch, which includes a complete list of all hardware that has ever been 037 * on the system), network logon preferences, network security information, software-related information (such as server 038 * names and the location of the server), and other system information. 039 * See <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx">Predefined Keys</a> 040 */ 041 public static final int HKEY_LOCAL_MACHINE = 0x80000002; 042 043 private static final long REG_SUCCESS = 0L; 044 045 private static final int KEY_READ = 0x20019; 046 private static final Preferences userRoot = Preferences.userRoot(); 047 private static final Preferences systemRoot = Preferences.systemRoot(); 048 private static final Class<? extends Preferences> userClass = userRoot.getClass(); 049 private static final Method regOpenKey; 050 private static final Method regCloseKey; 051 private static final Method regQueryValueEx; 052 private static final Method regEnumValue; 053 private static final Method regQueryInfoKey; 054 private static final Method regEnumKeyEx; 055 056 private static boolean java11; 057 058 static { 059 regOpenKey = getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class); 060 regCloseKey = getDeclaredMethod("WindowsRegCloseKey", int.class); 061 regQueryValueEx = getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class); 062 regEnumValue = getDeclaredMethod("WindowsRegEnumValue", int.class, int.class, int.class); 063 regQueryInfoKey = getDeclaredMethod("WindowsRegQueryInfoKey1", int.class); 064 regEnumKeyEx = getDeclaredMethod("WindowsRegEnumKeyEx", int.class, int.class, int.class); 065 ReflectionUtils.setObjectsAccessible(regOpenKey, regCloseKey, regQueryValueEx, regEnumValue, regQueryInfoKey, regEnumKeyEx); 066 } 067 068 private static Method getDeclaredMethod(String name, Class<?>... parameterTypes) { 069 try { 070 return userClass.getDeclaredMethod(name, parameterTypes); 071 } catch (NoSuchMethodException e) { 072 if (parameterTypes.length > 0 && parameterTypes[0] == int.class) { 073 // JDK-8198899: change of signature in Java 11. Old signature to drop when we switch to Java 11 074 Class<?>[] parameterTypesCopy = Utils.copyArray(parameterTypes); 075 parameterTypesCopy[0] = long.class; 076 java11 = true; 077 return getDeclaredMethod(name, parameterTypesCopy); 078 } 079 Logging.log(Logging.LEVEL_ERROR, "Unable to find WindowsReg method", e); 080 return null; 081 } catch (RuntimeException e) { 082 Logging.log(Logging.LEVEL_ERROR, "Unable to get WindowsReg method", e); 083 return null; 084 } 085 } 086 087 private static Number hkey(int key) { 088 return java11 ? ((Number) Long.valueOf(key)) : ((Number) Integer.valueOf(key)); 089 } 090 091 private WinRegistry() { 092 // Hide default constructor for utilities classes 093 } 094 095 /** 096 * Read a value from key and value name 097 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 098 * @param key key name 099 * @param valueName value name 100 * @return the value 101 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 102 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 103 * @throws InvocationTargetException if the underlying method throws an exception 104 */ 105 public static String readString(int hkey, String key, String valueName) 106 throws IllegalAccessException, InvocationTargetException { 107 if (hkey == HKEY_LOCAL_MACHINE) { 108 return readString(systemRoot, hkey, key, valueName); 109 } else if (hkey == HKEY_CURRENT_USER) { 110 return readString(userRoot, hkey, key, valueName); 111 } else { 112 throw new IllegalArgumentException("hkey=" + hkey); 113 } 114 } 115 116 /** 117 * Read value(s) and value name(s) form given key 118 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 119 * @param key key name 120 * @return the value name(s) plus the value(s) 121 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 122 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 123 * @throws InvocationTargetException if the underlying method throws an exception 124 */ 125 public static Map<String, String> readStringValues(int hkey, String key) 126 throws IllegalAccessException, InvocationTargetException { 127 if (hkey == HKEY_LOCAL_MACHINE) { 128 return readStringValues(systemRoot, hkey, key); 129 } else if (hkey == HKEY_CURRENT_USER) { 130 return readStringValues(userRoot, hkey, key); 131 } else { 132 throw new IllegalArgumentException("hkey=" + hkey); 133 } 134 } 135 136 /** 137 * Read the value name(s) from a given key 138 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 139 * @param key key name 140 * @return the value name(s) 141 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 142 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 143 * @throws InvocationTargetException if the underlying method throws an exception 144 */ 145 public static List<String> readStringSubKeys(int hkey, String key) 146 throws IllegalAccessException, InvocationTargetException { 147 if (hkey == HKEY_LOCAL_MACHINE) { 148 return readStringSubKeys(systemRoot, hkey, key); 149 } else if (hkey == HKEY_CURRENT_USER) { 150 return readStringSubKeys(userRoot, hkey, key); 151 } else { 152 throw new IllegalArgumentException("hkey=" + hkey); 153 } 154 } 155 156 // ===================== 157 158 private static Number getNumber(Object array, int index) { 159 if (array instanceof int[]) { 160 return ((int[]) array)[index]; 161 } else if (array instanceof long[]) { 162 return ((long[]) array)[index]; 163 } 164 throw new IllegalArgumentException(); 165 } 166 167 private static String readString(Preferences root, int hkey, String key, String value) 168 throws IllegalAccessException, InvocationTargetException { 169 if (regOpenKey == null || regQueryValueEx == null || regCloseKey == null) { 170 return null; 171 } 172 // Need to capture both int[] (Java 8-10) and long[] (Java 11+) 173 Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 174 if (getNumber(handles, 1).longValue() != REG_SUCCESS) { 175 return null; 176 } 177 byte[] valb = (byte[]) regQueryValueEx.invoke(root, getNumber(handles, 0), toCstr(value)); 178 regCloseKey.invoke(root, getNumber(handles, 0)); 179 return (valb != null ? new String(valb, StandardCharsets.UTF_8).trim() : null); 180 } 181 182 private static Map<String, String> readStringValues(Preferences root, int hkey, String key) 183 throws IllegalAccessException, InvocationTargetException { 184 if (regOpenKey == null || regQueryInfoKey == null || regEnumValue == null || regCloseKey == null) { 185 return Collections.emptyMap(); 186 } 187 HashMap<String, String> results = new HashMap<>(); 188 // Need to capture both int[] (Java 8-10) and long[] (Java 11+) 189 Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 190 if (getNumber(handles, 1).longValue() != REG_SUCCESS) { 191 return Collections.emptyMap(); 192 } 193 // Need to capture both int[] (Java 8-10) and long[] (Java 11+) 194 Object info = regQueryInfoKey.invoke(root, getNumber(handles, 0)); 195 196 int count = getNumber(info, 0).intValue(); 197 int maxlen = getNumber(info, 3).intValue(); 198 for (int index = 0; index < count; index++) { 199 byte[] name = (byte[]) regEnumValue.invoke(root, getNumber(handles, 0), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); 200 String value = readString(hkey, key, new String(name, StandardCharsets.UTF_8)); 201 results.put(new String(name, StandardCharsets.UTF_8).trim(), value); 202 } 203 regCloseKey.invoke(root, getNumber(handles, 0)); 204 return Collections.unmodifiableMap(results); 205 } 206 207 private static List<String> readStringSubKeys(Preferences root, int hkey, String key) 208 throws IllegalAccessException, InvocationTargetException { 209 if (regOpenKey == null || regQueryInfoKey == null || regEnumKeyEx == null || regCloseKey == null) { 210 return Collections.emptyList(); 211 } 212 List<String> results = new ArrayList<>(); 213 // Need to capture both int[] (Java 8-10) and long[] (Java 11+) 214 Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 215 if (getNumber(handles, 1).longValue() != REG_SUCCESS) { 216 return Collections.emptyList(); 217 } 218 // Need to capture both int[] (Java 8-10) and long[] (Java 11+) 219 Object info = regQueryInfoKey.invoke(root, getNumber(handles, 0)); 220 221 int count = getNumber(info, 0).intValue(); 222 int maxlen = getNumber(info, 3).intValue(); 223 for (int index = 0; index < count; index++) { 224 byte[] name = (byte[]) regEnumKeyEx.invoke(root, getNumber(handles, 0), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); 225 results.add(new String(name, StandardCharsets.UTF_8).trim()); 226 } 227 regCloseKey.invoke(root, getNumber(handles, 0)); 228 return Collections.unmodifiableList(results); 229 } 230 231 // utility 232 private static byte[] toCstr(String str) { 233 byte[] array = str.getBytes(StandardCharsets.UTF_8); 234 byte[] biggerCopy = Arrays.copyOf(array, array.length + 1); 235 biggerCopy[array.length] = 0; 236 return biggerCopy; 237 } 238}