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 int REG_SUCCESS = 0;
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    static {
057        try {
058            regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class);
059            regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", int.class);
060            regQueryValueEx = userClass.getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class);
061            regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", int.class, int.class, int.class);
062            regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", int.class);
063            regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", int.class, int.class, int.class);
064            Utils.setObjectsAccessible(regOpenKey, regCloseKey, regQueryValueEx, regEnumValue, regQueryInfoKey, regEnumKeyEx);
065        } catch (RuntimeException | ReflectiveOperationException e) {
066            throw new JosmRuntimeException(e);
067        }
068    }
069
070    private WinRegistry() {
071        // Hide default constructor for utilities classes
072    }
073
074    /**
075     * Read a value from key and value name
076     * @param hkey  HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
077     * @param key  key name
078     * @param valueName  value name
079     * @return the value
080     * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
081     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
082     * @throws InvocationTargetException if the underlying method throws an exception
083     */
084    public static String readString(int hkey, String key, String valueName)
085            throws IllegalAccessException, InvocationTargetException {
086        if (hkey == HKEY_LOCAL_MACHINE) {
087            return readString(systemRoot, hkey, key, valueName);
088        } else if (hkey == HKEY_CURRENT_USER) {
089            return readString(userRoot, hkey, key, valueName);
090        } else {
091            throw new IllegalArgumentException("hkey=" + hkey);
092        }
093    }
094
095    /**
096     * Read value(s) and value name(s) form given key
097     * @param hkey  HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
098     * @param key  key name
099     * @return the value name(s) plus the value(s)
100     * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
101     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
102     * @throws InvocationTargetException if the underlying method throws an exception
103     */
104    public static Map<String, String> readStringValues(int hkey, String key)
105            throws IllegalAccessException, InvocationTargetException {
106        if (hkey == HKEY_LOCAL_MACHINE) {
107            return readStringValues(systemRoot, hkey, key);
108        } else if (hkey == HKEY_CURRENT_USER) {
109            return readStringValues(userRoot, hkey, key);
110        } else {
111            throw new IllegalArgumentException("hkey=" + hkey);
112        }
113    }
114
115    /**
116     * Read the value name(s) from a given key
117     * @param hkey  HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
118     * @param key  key name
119     * @return the value name(s)
120     * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
121     * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
122     * @throws InvocationTargetException if the underlying method throws an exception
123     */
124    public static List<String> readStringSubKeys(int hkey, String key)
125            throws IllegalAccessException, InvocationTargetException {
126        if (hkey == HKEY_LOCAL_MACHINE) {
127            return readStringSubKeys(systemRoot, hkey, key);
128        } else if (hkey == HKEY_CURRENT_USER) {
129            return readStringSubKeys(userRoot, hkey, key);
130        } else {
131            throw new IllegalArgumentException("hkey=" + hkey);
132        }
133    }
134
135    // =====================
136
137    private static String readString(Preferences root, int hkey, String key, String value)
138            throws IllegalAccessException, InvocationTargetException {
139        int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
140        if (handles[1] != REG_SUCCESS) {
141            return null;
142        }
143        byte[] valb = (byte[]) regQueryValueEx.invoke(root, Integer.valueOf(handles[0]), toCstr(value));
144        regCloseKey.invoke(root, Integer.valueOf(handles[0]));
145        return (valb != null ? new String(valb, StandardCharsets.UTF_8).trim() : null);
146    }
147
148    private static Map<String, String> readStringValues(Preferences root, int hkey, String key)
149            throws IllegalAccessException, InvocationTargetException {
150        HashMap<String, String> results = new HashMap<>();
151        int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
152        if (handles[1] != REG_SUCCESS) {
153            return null;
154        }
155        int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0]));
156
157        int count = info[0]; // count
158        int maxlen = info[3]; // value length max
159        for (int index = 0; index < count; index++) {
160            byte[] name = (byte[]) regEnumValue.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1));
161            String value = readString(hkey, key, new String(name, StandardCharsets.UTF_8));
162            results.put(new String(name, StandardCharsets.UTF_8).trim(), value);
163        }
164        regCloseKey.invoke(root, Integer.valueOf(handles[0]));
165        return results;
166    }
167
168    private static List<String> readStringSubKeys(Preferences root, int hkey, String key)
169            throws IllegalAccessException, InvocationTargetException {
170        List<String> results = new ArrayList<>();
171        int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
172        if (handles[1] != REG_SUCCESS) {
173            return Collections.emptyList();
174        }
175        int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0]));
176
177        int count = info[0]; // Fix: info[2] was being used here with wrong results. Suggested by davenpcj, confirmed by Petrucio
178        int maxlen = info[3]; // value length max
179        for (int index = 0; index < count; index++) {
180            byte[] name = (byte[]) regEnumKeyEx.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1));
181            results.add(new String(name, StandardCharsets.UTF_8).trim());
182        }
183        regCloseKey.invoke(root, Integer.valueOf(handles[0]));
184        return results;
185    }
186
187    // utility
188    private static byte[] toCstr(String str) {
189        byte[] array = str.getBytes(StandardCharsets.UTF_8);
190        byte[] biggerCopy = Arrays.copyOf(array, array.length + 1);
191        biggerCopy[array.length] = 0;
192        return biggerCopy;
193    }
194}