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        Utils.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 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 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}