001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import static java.awt.event.InputEvent.ALT_DOWN_MASK;
005import static java.awt.event.InputEvent.CTRL_DOWN_MASK;
006import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
007import static java.awt.event.KeyEvent.VK_A;
008import static java.awt.event.KeyEvent.VK_C;
009import static java.awt.event.KeyEvent.VK_D;
010import static java.awt.event.KeyEvent.VK_DELETE;
011import static java.awt.event.KeyEvent.VK_DOWN;
012import static java.awt.event.KeyEvent.VK_ENTER;
013import static java.awt.event.KeyEvent.VK_ESCAPE;
014import static java.awt.event.KeyEvent.VK_F10;
015import static java.awt.event.KeyEvent.VK_F4;
016import static java.awt.event.KeyEvent.VK_LEFT;
017import static java.awt.event.KeyEvent.VK_NUM_LOCK;
018import static java.awt.event.KeyEvent.VK_PRINTSCREEN;
019import static java.awt.event.KeyEvent.VK_RIGHT;
020import static java.awt.event.KeyEvent.VK_SHIFT;
021import static java.awt.event.KeyEvent.VK_SPACE;
022import static java.awt.event.KeyEvent.VK_TAB;
023import static java.awt.event.KeyEvent.VK_UP;
024import static java.awt.event.KeyEvent.VK_V;
025import static java.awt.event.KeyEvent.VK_X;
026import static java.awt.event.KeyEvent.VK_Y;
027import static java.awt.event.KeyEvent.VK_Z;
028import static org.openstreetmap.josm.tools.I18n.tr;
029
030import java.awt.GraphicsEnvironment;
031import java.io.BufferedWriter;
032import java.io.File;
033import java.io.FileInputStream;
034import java.io.IOException;
035import java.io.OutputStream;
036import java.io.OutputStreamWriter;
037import java.io.Writer;
038import java.nio.charset.StandardCharsets;
039import java.nio.file.DirectoryStream;
040import java.nio.file.FileSystems;
041import java.nio.file.Files;
042import java.nio.file.Path;
043import java.security.InvalidKeyException;
044import java.security.KeyFactory;
045import java.security.KeyStore;
046import java.security.KeyStoreException;
047import java.security.NoSuchAlgorithmException;
048import java.security.NoSuchProviderException;
049import java.security.PublicKey;
050import java.security.SignatureException;
051import java.security.cert.CertificateException;
052import java.security.spec.InvalidKeySpecException;
053import java.security.spec.X509EncodedKeySpec;
054import java.util.ArrayList;
055import java.util.Collection;
056import java.util.Enumeration;
057import java.util.List;
058import java.util.Locale;
059import java.util.Properties;
060
061import javax.swing.JOptionPane;
062
063import org.openstreetmap.josm.Main;
064import org.openstreetmap.josm.data.Preferences;
065
066/**
067  * {@code PlatformHook} implementation for Microsoft Windows systems.
068  * @since 1023
069  */
070public class PlatformHookWindows implements PlatformHook {
071
072    /**
073     * Simple data class to hold information about a font.
074     *
075     * Used for fontconfig.properties files.
076     */
077    public static class FontEntry {
078        /**
079         * The character subset. Basically a free identifier, but should be unique.
080         */
081        @Preferences.pref
082        public String charset;
083
084        /**
085         * Platform font name.
086         */
087        @Preferences.pref
088        @Preferences.writeExplicitly
089        public String name = "";
090
091        /**
092         * File name.
093         */
094        @Preferences.pref
095        @Preferences.writeExplicitly
096        public String file = "";
097
098        /**
099         * Constructs a new {@code FontEntry}.
100         */
101        public FontEntry() {
102            // Default constructor needed for construction by reflection
103        }
104
105        /**
106         * Constructs a new {@code FontEntry}.
107         * @param charset The character subset. Basically a free identifier, but should be unique
108         * @param name Platform font name
109         * @param file File name
110         */
111        public FontEntry(String charset, String name, String file) {
112            this.charset = charset;
113            this.name = name;
114            this.file = file;
115        }
116    }
117
118    private static final byte[] INSECURE_PUBLIC_KEY = new byte[] {
119        0x30, (byte) 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 0x9, 0x2a, (byte) 0x86, 0x48,
120        (byte) 0x86, (byte) 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, (byte) 0x82, 0x1, 0xf, 0x0,
121        0x30, (byte) 0x82, 0x01, 0x0a, 0x02, (byte) 0x82, 0x01, 0x01, 0x00, (byte) 0x95, (byte) 0x95, (byte) 0x88,
122        (byte) 0x84, (byte) 0xc8, (byte) 0xd9, 0x6b, (byte) 0xc5, (byte) 0xda, 0x0b, 0x69, (byte) 0xbf, (byte) 0xfc,
123        0x7e, (byte) 0xb9, (byte) 0x96, 0x2c, (byte) 0xeb, (byte) 0x8f, (byte) 0xbc, 0x6e, 0x40, (byte) 0xe6, (byte) 0xe2,
124        (byte) 0xfc, (byte) 0xf1, 0x7f, 0x73, (byte) 0xa7, (byte) 0x9d, (byte) 0xde, (byte) 0xc7, (byte) 0x88, 0x57, 0x51,
125        (byte) 0x84, (byte) 0xed, (byte) 0x96, (byte) 0xfb, (byte) 0xe1, 0x38, (byte) 0xef, 0x08, 0x2b, (byte) 0xf3,
126        (byte) 0xc7, (byte) 0xc3, 0x5d, (byte) 0xfe, (byte) 0xf9, 0x51, (byte) 0xe6, 0x29, (byte) 0xfc, (byte) 0xe5, 0x0d,
127        (byte) 0xa1, 0x0d, (byte) 0xa8, (byte) 0xb4, (byte) 0xae, 0x26, 0x18, 0x19, 0x4d, 0x6c, 0x0c, 0x3b, 0x12, (byte) 0xba,
128        (byte) 0xbc, 0x5f, 0x32, (byte) 0xb3, (byte) 0xbe, (byte) 0x9d, 0x17, 0x0d, 0x4d, 0x2f, 0x1a, 0x48, (byte) 0xb7,
129        (byte) 0xac, (byte) 0xf7, 0x1a, 0x43, 0x01, (byte) 0x97, (byte) 0xf4, (byte) 0xf8, 0x4c, (byte) 0xbb, 0x6a, (byte) 0xbc,
130        0x33, (byte) 0xe1, 0x73, 0x1e, (byte) 0x86, (byte) 0xfb, 0x2e, (byte) 0xb1, 0x63, 0x75, (byte) 0x85, (byte) 0xdc,
131        (byte) 0x82, 0x6c, 0x28, (byte) 0xf1, (byte) 0xe3, (byte) 0x90, 0x63, (byte) 0x9d, 0x3d, 0x48, (byte) 0x8a, (byte) 0x8c,
132        0x47, (byte) 0xe2, 0x10, 0x0b, (byte) 0xef, (byte) 0x91, (byte) 0x94, (byte) 0xb0, 0x6c, 0x4c, (byte) 0x80, 0x76, 0x03,
133        (byte) 0xe1, (byte) 0xb6, (byte) 0x90, (byte) 0x87, (byte) 0xd9, (byte) 0xae, (byte) 0xf4, (byte) 0x8e, (byte) 0xe0,
134        (byte) 0x9f, (byte) 0xe7, 0x3a, 0x2c, 0x2f, 0x21, (byte) 0xd4, 0x46, (byte) 0xba, (byte) 0x95, 0x70, (byte) 0xa9, 0x5b,
135        0x20, 0x2a, (byte) 0xfa, 0x52, 0x3e, (byte) 0x9d, (byte) 0xd9, (byte) 0xef, 0x28, (byte) 0xc5, (byte) 0xd1, 0x60,
136        (byte) 0x89, 0x68, 0x6e, 0x7f, (byte) 0xd7, (byte) 0x9e, (byte) 0x89, 0x4c, (byte) 0xeb, 0x4d, (byte) 0xd2, (byte) 0xc6,
137        (byte) 0xf4, 0x2d, 0x02, 0x5d, (byte) 0xda, (byte) 0xde, 0x33, (byte) 0xfe, (byte) 0xc1, 0x7e, (byte) 0xde, 0x4f, 0x1f,
138        (byte) 0x9b, 0x6e, 0x6f, 0x0f, 0x66, 0x71, 0x19, (byte) 0xe9, 0x43, 0x3c, (byte) 0x83, 0x0a, 0x0f, 0x28, 0x21, (byte) 0xc8,
139        0x38, (byte) 0xd3, 0x4e, 0x48, (byte) 0xdf, (byte) 0xd4, (byte) 0x99, (byte) 0xb5, (byte) 0xc6, (byte) 0x8d, (byte) 0xd4,
140        (byte) 0xc1, 0x69, 0x58, 0x79, (byte) 0x82, 0x32, (byte) 0x82, (byte) 0xd4, (byte) 0x86, (byte) 0xe2, 0x04, 0x08, 0x63,
141        (byte) 0x87, (byte) 0xf0, 0x2a, (byte) 0xf6, (byte) 0xec, 0x3e, 0x51, 0x0f, (byte) 0xda, (byte) 0xb4, 0x67, 0x19, 0x5e,
142        0x16, 0x02, (byte) 0x9f, (byte) 0xf1, 0x19, 0x0c, 0x3e, (byte) 0xb8, 0x04, 0x49, 0x07, 0x53, 0x02, 0x03, 0x01, 0x00, 0x01
143    };
144
145    private static final String WINDOWS_ROOT = "Windows-ROOT";
146
147    @Override
148    public void afterPrefStartupHook() {
149        extendFontconfig("fontconfig.properties.src");
150    }
151
152    @Override
153    public void openUrl(String url) throws IOException {
154        Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
155    }
156
157    @Override
158    public void initSystemShortcuts() {
159        // CHECKSTYLE.OFF: LineLength
160        //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK);
161        Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results
162
163        // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts
164
165        // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all
166
167        // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page
168        Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic();
169
170        // Ease of Access keyboard shortcuts
171        Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off
172        Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off
173        //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?)
174
175        // General keyboard shortcuts
176        //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0);                            // Display Help
177        Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK);                // Copy the selected item
178        Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK);                 // Cut the selected item
179        Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK);               // Paste the selected item
180        Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK);                // Undo an action
181        Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK);                // Redo an action
182        //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0);                  // Delete the selected item and move it to the Recycle Bin
183        //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK);    // Delete the selected item without moving it to the Recycle Bin first
184        //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0);                          // Rename the selected item
185        Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK);  // Move the cursor to the beginning of the next word
186        Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the previous word
187        Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK);    // Move the cursor to the beginning of the next paragraph
188        Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK);        // Move the cursor to the beginning of the previous paragraph
189        //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
190        //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
191        //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);  // Select a block of text
192        //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK);    // Select a block of text
193        //Shortcut.registerSystemCut("microsoft-reserved-21", tr("reserved"), VK_RIGHT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document
194        //Shortcut.registerSystemCut("microsoft-reserved-22", tr("reserved"), VK_LEFT, SHIFT_DOWN_MASK);  // Select more than one item in a window or on the desktop, or select text within a document
195        //Shortcut.registerSystemCut("microsoft-reserved-23", tr("reserved"), VK_DOWN, SHIFT_DOWN_MASK);  // Select more than one item in a window or on the desktop, or select text within a document
196        //Shortcut.registerSystemCut("microsoft-reserved-24", tr("reserved"), VK_UP, SHIFT_DOWN_MASK);    // Select more than one item in a window or on the desktop, or select text within a document
197        //Shortcut.registerSystemCut("microsoft-reserved-25", tr("reserved"), VK_RIGHT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
198        //Shortcut.registerSystemCut("microsoft-reserved-26", tr("reserved"), VK_LEFT+, CTRL_DOWN_MASK);  // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
199        //Shortcut.registerSystemCut("microsoft-reserved-27", tr("reserved"), VK_DOWN+, CTRL_DOWN_MASK);  // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
200        //Shortcut.registerSystemCut("microsoft-reserved-28", tr("reserved"), VK_UP+, CTRL_DOWN_MASK);    // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?)
201        Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK);           // Select all items in a document or window
202        //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0);                          // Search for a file or folder
203        Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic();   // Display properties for the selected item
204        Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program
205        Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic();   // Open the shortcut menu for the active window
206        //Shortcut.registerSystemCut("microsoft-reserved-34", tr("reserved"), VK_F4, CTRL_DOWN_MASK);     // Close the active document (in programs that allow you to have multiple documents open simultaneously)
207        Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic();     // Switch between open items
208        Shortcut.registerSystemShortcut("microsoft-reserved-36", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Use the arrow keys to switch between open items
209        //Shortcut.registerSystemCut("microsoft-reserved-37", tr("reserved"), VK_TAB, ); // Cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Windows-Tab, how to handle it in Java ?)
210        //Shortcut.registerSystemCut("microsoft-reserved-38", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ); // Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Ctrl-Windows-Tab, how to handle it in Java ?)
211        Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic();  // Cycle through items in the order in which they were opened
212        //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0);                  // Cycle through screen elements in a window or on the desktop
213        //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0);                  // Display the address bar list in Windows Explorer
214        Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK);   // Display the shortcut menu for the selected item
215        Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu
216        //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0);                 // Activate the menu bar in the active program
217        //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0);               // Open the next menu to the right, or open a submenu
218        //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0);                // Open the next menu to the left, or close a submenu
219        //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0);                  // Refresh the active window
220        //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK);      // View the folder one level up in Windows Explorer
221        //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0);              // Cancel the current task
222        Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager
223        Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic();   // Switch the input language when multiple input languages are enabled
224        Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic();  // Switch the keyboard layout when multiple keyboard layouts are enabled
225        //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear)
226        // CHECKSTYLE.ON: LineLength
227    }
228
229    @Override
230    public String getDefaultStyle() {
231        return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
232    }
233
234    @Override
235    public boolean rename(File from, File to) {
236        if (to.exists())
237            Utils.deleteFile(to);
238        return from.renameTo(to);
239    }
240
241    @Override
242    public String getOSDescription() {
243        return Utils.strip(System.getProperty("os.name")) + ' ' +
244                ((System.getenv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit";
245    }
246
247    /**
248     * Loads Windows-ROOT keystore.
249     * @return Windows-ROOT keystore
250     * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
251     * @throws CertificateException if any of the certificates in the keystore could not be loaded
252     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
253     * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
254     * @since 7343
255     */
256    public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
257        KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
258        ks.load(null, null);
259        return ks;
260    }
261
262    /**
263     * Removes potential insecure certificates installed with previous versions of JOSM on Windows.
264     * @throws NoSuchAlgorithmException on unsupported signature algorithms
265     * @throws CertificateException if any of the certificates in the Windows keystore could not be loaded
266     * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the type "Windows-ROOT"
267     * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
268     * @since 7335
269     */
270    public static void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
271        // We offered before a public private key we need now to remove from Windows PCs as it might be a huge security risk (see #10230)
272        PublicKey insecurePubKey = null;
273        try {
274            insecurePubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(INSECURE_PUBLIC_KEY));
275        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
276            Main.error(e);
277            return;
278        }
279        KeyStore ks = getRootKeystore();
280        Enumeration<String> en = ks.aliases();
281        Collection<String> insecureCertificates = new ArrayList<>();
282        while (en.hasMoreElements()) {
283            String alias = en.nextElement();
284            // Look for certificates associated with a private key
285            if (ks.isKeyEntry(alias)) {
286                try {
287                    ks.getCertificate(alias).verify(insecurePubKey);
288                    // If no exception, this is a certificate signed with the insecure key -> remove it
289                    insecureCertificates.add(alias);
290                } catch (InvalidKeyException | NoSuchProviderException | SignatureException e) {
291                    // If exception this is not a certificate related to JOSM, just trace it
292                    Main.trace(alias + " --> " + e.getClass().getName());
293                    Main.trace(e);
294                }
295            }
296        }
297        // Remove insecure certificates
298        if (!insecureCertificates.isEmpty()) {
299            StringBuilder message = new StringBuilder("<html>");
300            message.append(tr("A previous version of JOSM has installed a custom certificate "+
301                    "in order to provide HTTPS support for Remote Control:"))
302                   .append("<br><ul>");
303            for (String alias : insecureCertificates) {
304                message.append("<li>")
305                       .append(alias)
306                       .append("</li>");
307            }
308            message.append("</ul>")
309                   .append(tr("It appears it could be an important <b>security risk</b>.<br><br>"+
310                    "You are now going to be prompted by Windows to remove this insecure certificate.<br>"+
311                    "For your own safety, <b>please click Yes</b> in next dialog."))
312                   .append("</html>");
313            JOptionPane.showMessageDialog(Main.parent, message.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE);
314            for (String alias : insecureCertificates) {
315                Main.warn(tr("Removing insecure certificate from {0} keystore: {1}", WINDOWS_ROOT, alias));
316                try {
317                    ks.deleteEntry(alias);
318                } catch (KeyStoreException e) {
319                    Main.error(e, tr("Unable to remove insecure certificate from keystore: {0}", e.getMessage()));
320                }
321            }
322        }
323    }
324
325    @Override
326    public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
327            throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
328        KeyStore ks = getRootKeystore();
329        // Look for certificate to install
330        String alias = ks.getCertificateAlias(trustedCert.getTrustedCertificate());
331        if (alias != null) {
332            // JOSM certificate found, return
333            Main.debug(tr("JOSM localhost certificate found in {0} keystore: {1}", WINDOWS_ROOT, alias));
334            return false;
335        }
336        if (!GraphicsEnvironment.isHeadless()) {
337            // JOSM certificate not found, warn user
338            StringBuilder message = new StringBuilder("<html>");
339            message.append(tr("Remote Control is configured to provide HTTPS support.<br>"+
340                    "This requires to add a custom certificate generated by JOSM to the Windows Root CA store.<br><br>"+
341                    "You are now going to be prompted by Windows to confirm this operation.<br>"+
342                    "To enable proper HTTPS support, <b>please click Yes</b> in next dialog.<br><br>"+
343                    "If unsure, you can also click No then disable HTTPS support in Remote Control preferences."))
344                   .append("</html>");
345            JOptionPane.showMessageDialog(Main.parent, message.toString(),
346                    tr("HTTPS support in Remote Control"), JOptionPane.INFORMATION_MESSAGE);
347        }
348        // install it to Windows-ROOT keystore, used by IE, Chrome and Safari, but not by Firefox
349        Main.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
350        ks.setEntry(entryAlias, trustedCert, null);
351        return true;
352    }
353
354    @Override
355    public File getDefaultCacheDirectory() {
356        String p = System.getenv("LOCALAPPDATA");
357        if (p == null || p.isEmpty()) {
358            // Fallback for Windows OS earlier than Windows Vista, where the variable is not defined
359            p = System.getenv("APPDATA");
360        }
361        return new File(new File(p, Main.pref.getJOSMDirectoryBaseName()), "cache");
362    }
363
364    @Override
365    public File getDefaultPrefDirectory() {
366        return new File(System.getenv("APPDATA"), Main.pref.getJOSMDirectoryBaseName());
367    }
368
369    @Override
370    public File getDefaultUserDataDirectory() {
371        // Use preferences directory by default
372        return Main.pref.getPreferencesDirectory();
373    }
374
375    /**
376     * <p>Add more fallback fonts to the Java runtime, in order to get
377     * support for more scripts.</p>
378     *
379     * <p>The font configuration in Java doesn't include some Indic scripts,
380     * even though MS Windows ships with fonts that cover these unicode ranges.</p>
381     *
382     * <p>To fix this, the fontconfig.properties template is copied to the JOSM
383     * cache folder. Then, the additional entries are added to the font
384     * configuration. Finally the system property "sun.awt.fontconfig" is set
385     * to the customized fontconfig.properties file.</p>
386     *
387     * <p>This is a crude hack, but better than no font display at all for these languages.
388     * There is no guarantee, that the template file
389     * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default
390     * configuration (which is in a binary format).
391     * Furthermore, the system property "sun.awt.fontconfig" is undocumented and
392     * may no longer work in future versions of Java.</p>
393     *
394     * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p>
395     *
396     * @param templateFileName file name of the fontconfig.properties template file
397     */
398    protected void extendFontconfig(String templateFileName) {
399        String customFontconfigFile = Main.pref.get("fontconfig.properties", null);
400        if (customFontconfigFile != null) {
401            Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile);
402            return;
403        }
404        if (!Main.pref.getBoolean("font.extended-unicode", true))
405            return;
406
407        String javaLibPath = System.getProperty("java.home") + File.separator + "lib";
408        Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName);
409        if (!Files.isReadable(templateFile)) {
410            Main.warn("extended font config - unable to find font config template file {0}", templateFile.toString());
411            return;
412        }
413        try (FileInputStream fis = new FileInputStream(templateFile.toFile())) {
414            Properties props = new Properties();
415            props.load(fis);
416            byte[] content = Files.readAllBytes(templateFile);
417            File cachePath = Main.pref.getCacheDirectory();
418            Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties");
419            OutputStream os = Files.newOutputStream(fontconfigFile);
420            os.write(content);
421            try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
422                Collection<FontEntry> extrasPref = Main.pref.getListOfStructs(
423                        "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class);
424                Collection<FontEntry> extras = new ArrayList<>();
425                w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n");
426                List<String> allCharSubsets = new ArrayList<>();
427                for (FontEntry entry: extrasPref) {
428                    Collection<String> fontsAvail = getInstalledFonts();
429                    if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) {
430                        if (!allCharSubsets.contains(entry.charset)) {
431                            allCharSubsets.add(entry.charset);
432                            extras.add(entry);
433                        } else {
434                            Main.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''",
435                                    entry.charset, entry.name);
436                        }
437                    } else {
438                        Main.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name);
439                    }
440                }
441                for (FontEntry entry: extras) {
442                    allCharSubsets.add(entry.charset);
443                    if ("".equals(entry.name)) {
444                        continue;
445                    }
446                    String key = "allfonts." + entry.charset;
447                    String value = entry.name;
448                    String prevValue = props.getProperty(key);
449                    if (prevValue != null && !prevValue.equals(value)) {
450                        Main.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
451                    }
452                    w.append(key + '=' + value + '\n');
453                }
454                w.append('\n');
455                for (FontEntry entry: extras) {
456                    if ("".equals(entry.name) || "".equals(entry.file)) {
457                        continue;
458                    }
459                    String key = "filename." + entry.name.replace(' ', '_');
460                    String value = entry.file;
461                    String prevValue = props.getProperty(key);
462                    if (prevValue != null && !prevValue.equals(value)) {
463                        Main.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
464                    }
465                    w.append(key + '=' + value + '\n');
466                }
467                w.append('\n');
468                String fallback = props.getProperty("sequence.fallback");
469                if (fallback != null) {
470                    w.append("sequence.fallback=" + fallback + ',' + Utils.join(",", allCharSubsets) + '\n');
471                } else {
472                    w.append("sequence.fallback=" + Utils.join(",", allCharSubsets) + '\n');
473                }
474            }
475            Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString());
476        } catch (IOException ex) {
477            Main.error(ex);
478        }
479    }
480
481    /**
482     * Get a list of fonts that are installed on the system.
483     *
484     * Must be done without triggering the Java Font initialization.
485     * (See {@link #extendFontconfig(java.lang.String)}, have to set system
486     * property first, which is then read by sun.awt.FontConfiguration upon initialization.)
487     *
488     * @return list of file names
489     */
490    protected Collection<String> getInstalledFonts() {
491        // Cannot use GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()
492        // because we have to set the system property before Java initializes its fonts.
493        // Use more low-level method to find the installed fonts.
494        List<String> fontsAvail = new ArrayList<>();
495        Path fontPath = FileSystems.getDefault().getPath(System.getenv("SYSTEMROOT"), "Fonts");
496        try (DirectoryStream<Path> ds = Files.newDirectoryStream(fontPath)) {
497            for (Path p : ds) {
498                Path filename = p.getFileName();
499                if (filename != null) {
500                    fontsAvail.add(filename.toString().toUpperCase(Locale.ENGLISH));
501                }
502            }
503            fontsAvail.add(""); // for devanagari
504        } catch (IOException ex) {
505            Main.error(ex, false);
506            Main.warn("extended font config - failed to load available Fonts");
507            fontsAvail = null;
508        }
509        return fontsAvail;
510    }
511
512    /**
513     * Get default list of additional fonts to add to the configuration.
514     *
515     * Java will choose thee first font in the list that can render a certain character.
516     *
517     * @return list of FontEntry objects
518     */
519    protected Collection<FontEntry> getAdditionalFonts() {
520        Collection<FontEntry> def = new ArrayList<>(33);
521        def.add(new FontEntry("devanagari", "", "")); // just include in fallback list font already defined in template
522
523        // Windows scripts: https://msdn.microsoft.com/en-us/goglobal/bb688099.aspx
524        // IE default fonts: https://msdn.microsoft.com/en-us/library/ie/dn467844(v=vs.85).aspx
525
526        // Windows 10 and later
527        def.add(new FontEntry("historic", "Segoe UI Historic", "SEGUIHIS.TTF"));       // historic charsets
528
529        // Windows 8/8.1 and later
530        def.add(new FontEntry("javanese", "Javanese Text", "JAVATEXT.TTF"));           // ISO 639: jv
531        def.add(new FontEntry("leelawadee", "Leelawadee", "LEELAWAD.TTF"));            // ISO 639: bug
532        def.add(new FontEntry("myanmar", "Myanmar Text", "MMRTEXT.TTF"));              // ISO 639: my
533        def.add(new FontEntry("nirmala", "Nirmala UI", "NIRMALA.TTF"));                // ISO 639: sat,srb
534        def.add(new FontEntry("segoeui", "Segoe UI", "SEGOEUI.TTF"));                  // ISO 639: lis
535
536        // Windows 7 and later
537        def.add(new FontEntry("nko_tifinagh_vai_osmanya", "Ebrima", "EBRIMA.TTF"));    // ISO 639: ber. Nko only since Win 8
538        def.add(new FontEntry("khmer1", "Khmer UI", "KHMERUI.TTF"));                   // ISO 639: km
539        def.add(new FontEntry("lao1", "Lao UI", "LAOUI.TTF"));                         // ISO 639: lo
540        def.add(new FontEntry("tai_le", "Microsoft Tai Le", "TAILE.TTF"));             // ISO 639: khb
541        def.add(new FontEntry("new_tai_lue", "Microsoft New Tai Lue", "NTHAILU.TTF")); // ISO 639: khb
542
543        // Windows Vista and later:
544        def.add(new FontEntry("ethiopic", "Nyala", "NYALA.TTF"));                   // ISO 639: am,gez,ti
545        def.add(new FontEntry("tibetan", "Microsoft Himalaya", "HIMALAYA.TTF"));    // ISO 639: bo,dz
546        def.add(new FontEntry("cherokee", "Plantagenet Cherokee", "PLANTC.TTF"));   // ISO 639: chr
547        def.add(new FontEntry("unified_canadian", "Euphemia", "EUPHEMIA.TTF"));     // ISO 639: cr,in
548        def.add(new FontEntry("khmer2", "DaunPenh", "DAUNPENH.TTF"));               // ISO 639: km
549        def.add(new FontEntry("khmer3", "MoolBoran", "MOOLBOR.TTF"));               // ISO 639: km
550        def.add(new FontEntry("lao_thai", "DokChampa", "DOKCHAMP.TTF"));            // ISO 639: lo
551        def.add(new FontEntry("mongolian", "Mongolian Baiti", "MONBAITI.TTF"));     // ISO 639: mn
552        def.add(new FontEntry("oriya", "Kalinga", "KALINGA.TTF"));                  // ISO 639: or
553        def.add(new FontEntry("sinhala", "Iskoola Pota", "ISKPOTA.TTF"));           // ISO 639: si
554        def.add(new FontEntry("yi", "Yi Baiti", "MSYI.TTF"));                       // ISO 639: ii
555
556        // Windows XP and later
557        def.add(new FontEntry("gujarati", "Shruti", "SHRUTI.TTF"));
558        def.add(new FontEntry("kannada", "Tunga", "TUNGA.TTF"));
559        def.add(new FontEntry("gurmukhi", "Raavi", "RAAVI.TTF"));
560        def.add(new FontEntry("telugu", "Gautami", "GAUTAMI.TTF"));
561        def.add(new FontEntry("bengali", "Vrinda", "VRINDA.TTF"));                  // since XP SP2
562        def.add(new FontEntry("syriac", "Estrangelo Edessa", "ESTRE.TTF"));         // ISO 639: arc
563        def.add(new FontEntry("thaana", "MV Boli", "MVBOLI.TTF"));                  // ISO 639: dv
564        def.add(new FontEntry("malayalam", "Kartika", "KARTIKA.TTF"));              // ISO 639: ml; since XP SP2
565
566        // Windows 2000 and later
567        def.add(new FontEntry("tamil", "Latha", "LATHA.TTF"));
568
569        // Comes with MS Office & Outlook 2000. Good unicode coverage, so add if available.
570        def.add(new FontEntry("arialuni", "Arial Unicode MS", "ARIALUNI.TTF"));
571
572        return def;
573    }
574}