source: josm/trunk/src/org/openstreetmap/josm/tools/PlatformHookWindows.java@ 14431

Last change on this file since 14431 was 14431, checked in by Don-vip, 5 years ago

fix #17006 - improve startup time on Windows by reworking certificate fetching (patch by GerdP, modified)

  • Property svn:eol-style set to native
File size: 46.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import static java.awt.event.InputEvent.ALT_DOWN_MASK;
5import static java.awt.event.InputEvent.CTRL_DOWN_MASK;
6import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
7import static java.awt.event.KeyEvent.VK_A;
8import static java.awt.event.KeyEvent.VK_C;
9import static java.awt.event.KeyEvent.VK_D;
10import static java.awt.event.KeyEvent.VK_DELETE;
11import static java.awt.event.KeyEvent.VK_DOWN;
12import static java.awt.event.KeyEvent.VK_ENTER;
13import static java.awt.event.KeyEvent.VK_ESCAPE;
14import static java.awt.event.KeyEvent.VK_F10;
15import static java.awt.event.KeyEvent.VK_F4;
16import static java.awt.event.KeyEvent.VK_LEFT;
17import static java.awt.event.KeyEvent.VK_NUM_LOCK;
18import static java.awt.event.KeyEvent.VK_PRINTSCREEN;
19import static java.awt.event.KeyEvent.VK_RIGHT;
20import static java.awt.event.KeyEvent.VK_SHIFT;
21import static java.awt.event.KeyEvent.VK_SPACE;
22import static java.awt.event.KeyEvent.VK_TAB;
23import static java.awt.event.KeyEvent.VK_UP;
24import static java.awt.event.KeyEvent.VK_V;
25import static java.awt.event.KeyEvent.VK_X;
26import static java.awt.event.KeyEvent.VK_Y;
27import static java.awt.event.KeyEvent.VK_Z;
28import static org.openstreetmap.josm.tools.I18n.tr;
29import static org.openstreetmap.josm.tools.Utils.getSystemEnv;
30import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
31import static org.openstreetmap.josm.tools.WinRegistry.HKEY_LOCAL_MACHINE;
32
33import java.awt.Desktop;
34import java.awt.GraphicsEnvironment;
35import java.io.BufferedWriter;
36import java.io.File;
37import java.io.IOException;
38import java.io.InputStream;
39import java.io.OutputStream;
40import java.io.OutputStreamWriter;
41import java.io.Writer;
42import java.lang.reflect.InvocationTargetException;
43import java.net.URI;
44import java.net.URISyntaxException;
45import java.nio.charset.StandardCharsets;
46import java.nio.file.DirectoryIteratorException;
47import java.nio.file.DirectoryStream;
48import java.nio.file.FileSystems;
49import java.nio.file.Files;
50import java.nio.file.InvalidPathException;
51import java.nio.file.Path;
52import java.security.InvalidKeyException;
53import java.security.KeyFactory;
54import java.security.KeyStore;
55import java.security.KeyStoreException;
56import java.security.MessageDigest;
57import java.security.NoSuchAlgorithmException;
58import java.security.NoSuchProviderException;
59import java.security.PublicKey;
60import java.security.SignatureException;
61import java.security.cert.Certificate;
62import java.security.cert.CertificateException;
63import java.security.cert.X509Certificate;
64import java.security.spec.InvalidKeySpecException;
65import java.security.spec.X509EncodedKeySpec;
66import java.text.ParseException;
67import java.util.ArrayList;
68import java.util.Arrays;
69import java.util.Collection;
70import java.util.Enumeration;
71import java.util.HashSet;
72import java.util.List;
73import java.util.Locale;
74import java.util.Properties;
75import java.util.Set;
76import java.util.concurrent.ExecutionException;
77import java.util.concurrent.TimeUnit;
78import java.util.regex.Matcher;
79import java.util.regex.Pattern;
80
81import javax.swing.JOptionPane;
82
83import org.openstreetmap.josm.data.Preferences;
84import org.openstreetmap.josm.data.StructUtils;
85import org.openstreetmap.josm.data.StructUtils.StructEntry;
86import org.openstreetmap.josm.data.StructUtils.WriteExplicitly;
87import org.openstreetmap.josm.gui.MainApplication;
88import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
89import org.openstreetmap.josm.spi.preferences.Config;
90
91/**
92 * {@code PlatformHook} implementation for Microsoft Windows systems.
93 * @since 1023
94 */
95public class PlatformHookWindows implements PlatformHook {
96
97 /**
98 * Pattern of Microsoft .NET and Powershell version numbers in registry.
99 */
100 private static final Pattern MS_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+.*)?");
101
102 /**
103 * Simple data class to hold information about a font.
104 *
105 * Used for fontconfig.properties files.
106 */
107 public static class FontEntry {
108 /**
109 * The character subset. Basically a free identifier, but should be unique.
110 */
111 @StructEntry
112 public String charset;
113
114 /**
115 * Platform font name.
116 */
117 @StructEntry
118 @WriteExplicitly
119 public String name = "";
120
121 /**
122 * File name.
123 */
124 @StructEntry
125 @WriteExplicitly
126 public String file = "";
127
128 /**
129 * Constructs a new {@code FontEntry}.
130 */
131 public FontEntry() {
132 // Default constructor needed for construction by reflection
133 }
134
135 /**
136 * Constructs a new {@code FontEntry}.
137 * @param charset The character subset. Basically a free identifier, but should be unique
138 * @param name Platform font name
139 * @param file File name
140 */
141 public FontEntry(String charset, String name, String file) {
142 this.charset = charset;
143 this.name = name;
144 this.file = file;
145 }
146 }
147
148 private static final byte[] INSECURE_PUBLIC_KEY = new byte[] {
149 0x30, (byte) 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 0x9, 0x2a, (byte) 0x86, 0x48,
150 (byte) 0x86, (byte) 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, (byte) 0x82, 0x1, 0xf, 0x0,
151 0x30, (byte) 0x82, 0x01, 0x0a, 0x02, (byte) 0x82, 0x01, 0x01, 0x00, (byte) 0x95, (byte) 0x95, (byte) 0x88,
152 (byte) 0x84, (byte) 0xc8, (byte) 0xd9, 0x6b, (byte) 0xc5, (byte) 0xda, 0x0b, 0x69, (byte) 0xbf, (byte) 0xfc,
153 0x7e, (byte) 0xb9, (byte) 0x96, 0x2c, (byte) 0xeb, (byte) 0x8f, (byte) 0xbc, 0x6e, 0x40, (byte) 0xe6, (byte) 0xe2,
154 (byte) 0xfc, (byte) 0xf1, 0x7f, 0x73, (byte) 0xa7, (byte) 0x9d, (byte) 0xde, (byte) 0xc7, (byte) 0x88, 0x57, 0x51,
155 (byte) 0x84, (byte) 0xed, (byte) 0x96, (byte) 0xfb, (byte) 0xe1, 0x38, (byte) 0xef, 0x08, 0x2b, (byte) 0xf3,
156 (byte) 0xc7, (byte) 0xc3, 0x5d, (byte) 0xfe, (byte) 0xf9, 0x51, (byte) 0xe6, 0x29, (byte) 0xfc, (byte) 0xe5, 0x0d,
157 (byte) 0xa1, 0x0d, (byte) 0xa8, (byte) 0xb4, (byte) 0xae, 0x26, 0x18, 0x19, 0x4d, 0x6c, 0x0c, 0x3b, 0x12, (byte) 0xba,
158 (byte) 0xbc, 0x5f, 0x32, (byte) 0xb3, (byte) 0xbe, (byte) 0x9d, 0x17, 0x0d, 0x4d, 0x2f, 0x1a, 0x48, (byte) 0xb7,
159 (byte) 0xac, (byte) 0xf7, 0x1a, 0x43, 0x01, (byte) 0x97, (byte) 0xf4, (byte) 0xf8, 0x4c, (byte) 0xbb, 0x6a, (byte) 0xbc,
160 0x33, (byte) 0xe1, 0x73, 0x1e, (byte) 0x86, (byte) 0xfb, 0x2e, (byte) 0xb1, 0x63, 0x75, (byte) 0x85, (byte) 0xdc,
161 (byte) 0x82, 0x6c, 0x28, (byte) 0xf1, (byte) 0xe3, (byte) 0x90, 0x63, (byte) 0x9d, 0x3d, 0x48, (byte) 0x8a, (byte) 0x8c,
162 0x47, (byte) 0xe2, 0x10, 0x0b, (byte) 0xef, (byte) 0x91, (byte) 0x94, (byte) 0xb0, 0x6c, 0x4c, (byte) 0x80, 0x76, 0x03,
163 (byte) 0xe1, (byte) 0xb6, (byte) 0x90, (byte) 0x87, (byte) 0xd9, (byte) 0xae, (byte) 0xf4, (byte) 0x8e, (byte) 0xe0,
164 (byte) 0x9f, (byte) 0xe7, 0x3a, 0x2c, 0x2f, 0x21, (byte) 0xd4, 0x46, (byte) 0xba, (byte) 0x95, 0x70, (byte) 0xa9, 0x5b,
165 0x20, 0x2a, (byte) 0xfa, 0x52, 0x3e, (byte) 0x9d, (byte) 0xd9, (byte) 0xef, 0x28, (byte) 0xc5, (byte) 0xd1, 0x60,
166 (byte) 0x89, 0x68, 0x6e, 0x7f, (byte) 0xd7, (byte) 0x9e, (byte) 0x89, 0x4c, (byte) 0xeb, 0x4d, (byte) 0xd2, (byte) 0xc6,
167 (byte) 0xf4, 0x2d, 0x02, 0x5d, (byte) 0xda, (byte) 0xde, 0x33, (byte) 0xfe, (byte) 0xc1, 0x7e, (byte) 0xde, 0x4f, 0x1f,
168 (byte) 0x9b, 0x6e, 0x6f, 0x0f, 0x66, 0x71, 0x19, (byte) 0xe9, 0x43, 0x3c, (byte) 0x83, 0x0a, 0x0f, 0x28, 0x21, (byte) 0xc8,
169 0x38, (byte) 0xd3, 0x4e, 0x48, (byte) 0xdf, (byte) 0xd4, (byte) 0x99, (byte) 0xb5, (byte) 0xc6, (byte) 0x8d, (byte) 0xd4,
170 (byte) 0xc1, 0x69, 0x58, 0x79, (byte) 0x82, 0x32, (byte) 0x82, (byte) 0xd4, (byte) 0x86, (byte) 0xe2, 0x04, 0x08, 0x63,
171 (byte) 0x87, (byte) 0xf0, 0x2a, (byte) 0xf6, (byte) 0xec, 0x3e, 0x51, 0x0f, (byte) 0xda, (byte) 0xb4, 0x67, 0x19, 0x5e,
172 0x16, 0x02, (byte) 0x9f, (byte) 0xf1, 0x19, 0x0c, 0x3e, (byte) 0xb8, 0x04, 0x49, 0x07, 0x53, 0x02, 0x03, 0x01, 0x00, 0x01
173 };
174
175 private static final String WINDOWS_ROOT = "Windows-ROOT";
176
177 private static final String CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
178
179 private String oSBuildNumber;
180
181 @Override
182 public Platform getPlatform() {
183 return Platform.WINDOWS;
184 }
185
186 @Override
187 public void afterPrefStartupHook() {
188 extendFontconfig("fontconfig.properties.src");
189 }
190
191 @Override
192 public void startupHook(JavaExpirationCallback callback) {
193 checkExpiredJava(callback);
194 }
195
196 @Override
197 public void openUrl(String url) throws IOException {
198 final String customBrowser = Config.getPref().get("browser.windows", null);
199 if (customBrowser != null) {
200 Runtime.getRuntime().exec(new String[]{customBrowser, url});
201 return;
202 }
203 try {
204 // Desktop API works fine under Windows
205 Desktop.getDesktop().browse(new URI(url));
206 } catch (IOException | URISyntaxException e) {
207 Logging.log(Logging.LEVEL_WARN, "Desktop class failed. Platform dependent fall back for open url in browser.", e);
208 Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url});
209 }
210 }
211
212 @Override
213 public void initSystemShortcuts() {
214 // CHECKSTYLE.OFF: LineLength
215 //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK);
216 Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results
217
218 // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts
219
220 // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all
221
222 // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page
223 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic();
224
225 // Ease of Access keyboard shortcuts
226 Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off
227 Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off
228 //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?)
229
230 // General keyboard shortcuts
231 //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0); // Display Help
232 Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK); // Copy the selected item
233 Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK); // Cut the selected item
234 Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK); // Paste the selected item
235 Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK); // Undo an action
236 Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK); // Redo an action
237 //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0); // Delete the selected item and move it to the Recycle Bin
238 //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK); // Delete the selected item without moving it to the Recycle Bin first
239 //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0); // Rename the selected item
240 Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next word
241 Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous word
242 Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next paragraph
243 Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous paragraph
244 //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
245 //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
246 //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
247 //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
248 //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
249 //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
250 //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
251 //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
252 //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 ?)
253 //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 ?)
254 //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 ?)
255 //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 ?)
256 Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK); // Select all items in a document or window
257 //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0); // Search for a file or folder
258 Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic(); // Display properties for the selected item
259 Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program
260 Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic(); // Open the shortcut menu for the active window
261 //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)
262 Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic(); // Switch between open items
263 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
264 //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 ?)
265 //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 ?)
266 Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic(); // Cycle through items in the order in which they were opened
267 //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0); // Cycle through screen elements in a window or on the desktop
268 //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0); // Display the address bar list in Windows Explorer
269 Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK); // Display the shortcut menu for the selected item
270 Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu
271 //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0); // Activate the menu bar in the active program
272 //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0); // Open the next menu to the right, or open a submenu
273 //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0); // Open the next menu to the left, or close a submenu
274 //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0); // Refresh the active window
275 //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK); // View the folder one level up in Windows Explorer
276 //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0); // Cancel the current task
277 Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager
278 Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic(); // Switch the input language when multiple input languages are enabled
279 Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic(); // Switch the keyboard layout when multiple keyboard layouts are enabled
280 //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear)
281 // CHECKSTYLE.ON: LineLength
282 }
283
284 @Override
285 public String getDefaultStyle() {
286 return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
287 }
288
289 @Override
290 public boolean rename(File from, File to) {
291 if (to.exists())
292 Utils.deleteFile(to);
293 return from.renameTo(to);
294 }
295
296 @Override
297 public String getOSDescription() {
298 return Utils.strip(getSystemProperty("os.name")) + ' ' +
299 ((getSystemEnv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit";
300 }
301
302 /**
303 * Returns the Windows product name from registry (example: "Windows 10 Pro")
304 * @return the Windows product name from registry
305 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
306 * @throws InvocationTargetException if the underlying method throws an exception
307 * @since 12744
308 */
309 public static String getProductName() throws IllegalAccessException, InvocationTargetException {
310 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ProductName");
311 }
312
313 /**
314 * Returns the Windows release identifier from registry (example: "1703")
315 * @return the Windows release identifier from registry
316 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
317 * @throws InvocationTargetException if the underlying method throws an exception
318 * @since 12744
319 */
320 public static String getReleaseId() throws IllegalAccessException, InvocationTargetException {
321 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ReleaseId");
322 }
323
324 /**
325 * Returns the Windows current build number from registry (example: "15063")
326 * @return the Windows current build number from registry
327 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
328 * @throws InvocationTargetException if the underlying method throws an exception
329 * @since 12744
330 */
331 public static String getCurrentBuild() throws IllegalAccessException, InvocationTargetException {
332 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild");
333 }
334
335 private static String buildOSBuildNumber() {
336 StringBuilder sb = new StringBuilder();
337 try {
338 sb.append(getProductName());
339 String releaseId = getReleaseId();
340 if (releaseId != null) {
341 sb.append(' ').append(releaseId);
342 }
343 sb.append(" (").append(getCurrentBuild()).append(')');
344 } catch (ReflectiveOperationException | JosmRuntimeException | NoClassDefFoundError e) {
345 Logging.log(Logging.LEVEL_ERROR, "Unable to get Windows build number", e);
346 Logging.debug(e);
347 }
348 return sb.toString();
349 }
350
351 @Override
352 public String getOSBuildNumber() {
353 if (oSBuildNumber == null) {
354 oSBuildNumber = buildOSBuildNumber();
355 }
356 return oSBuildNumber;
357 }
358
359 /**
360 * Loads Windows-ROOT keystore.
361 * @return Windows-ROOT keystore
362 * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
363 * @throws CertificateException if any of the certificates in the keystore could not be loaded
364 * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
365 * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
366 * @since 7343
367 */
368 public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
369 KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
370 ks.load(null, null);
371 return ks;
372 }
373
374 /**
375 * Removes potential insecure certificates installed with previous versions of JOSM on Windows.
376 * @throws NoSuchAlgorithmException on unsupported signature algorithms
377 * @throws CertificateException if any of the certificates in the Windows keystore could not be loaded
378 * @throws KeyStoreException if no Provider supports a KeyStoreSpi implementation for the type "Windows-ROOT"
379 * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
380 * @since 7335
381 */
382 public static void removeInsecureCertificates() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
383 // 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)
384 PublicKey insecurePubKey = null;
385 try {
386 insecurePubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(INSECURE_PUBLIC_KEY));
387 } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
388 Logging.error(e);
389 return;
390 }
391 KeyStore ks = getRootKeystore();
392 Enumeration<String> en = ks.aliases();
393 Collection<String> insecureCertificates = new ArrayList<>();
394 while (en.hasMoreElements()) {
395 String alias = en.nextElement();
396 // Look for certificates associated with a private key
397 if (ks.isKeyEntry(alias)) {
398 try {
399 ks.getCertificate(alias).verify(insecurePubKey);
400 // If no exception, this is a certificate signed with the insecure key -> remove it
401 insecureCertificates.add(alias);
402 } catch (InvalidKeyException | NoSuchProviderException | SignatureException e) {
403 // If exception this is not a certificate related to JOSM, just trace it
404 Logging.trace(alias + " --> " + e.getClass().getName());
405 Logging.trace(e);
406 }
407 }
408 }
409 // Remove insecure certificates
410 if (!insecureCertificates.isEmpty()) {
411 StringBuilder message = new StringBuilder("<html>");
412 message.append(tr("A previous version of JOSM has installed a custom certificate "+
413 "in order to provide HTTPS support for Remote Control:"))
414 .append("<br><ul>");
415 for (String alias : insecureCertificates) {
416 message.append("<li>")
417 .append(alias)
418 .append("</li>");
419 }
420 message.append("</ul>")
421 .append(tr("It appears it could be an important <b>security risk</b>.<br><br>"+
422 "You are now going to be prompted by Windows to remove this insecure certificate.<br>"+
423 "For your own safety, <b>please click Yes</b> in next dialog."))
424 .append("</html>");
425 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), message.toString(), tr("Warning"), JOptionPane.WARNING_MESSAGE);
426 for (String alias : insecureCertificates) {
427 Logging.warn(tr("Removing insecure certificate from {0} keystore: {1}", WINDOWS_ROOT, alias));
428 try {
429 ks.deleteEntry(alias);
430 } catch (KeyStoreException e) {
431 Logging.log(Logging.LEVEL_ERROR, tr("Unable to remove insecure certificate from keystore: {0}", e.getMessage()), e);
432 }
433 }
434 }
435 }
436
437 @Override
438 public boolean setupHttpsCertificate(String entryAlias, KeyStore.TrustedCertificateEntry trustedCert)
439 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
440 KeyStore ks = getRootKeystore();
441 // Look for certificate to install
442 try {
443 String alias = ks.getCertificateAlias(trustedCert.getTrustedCertificate());
444 if (alias != null) {
445 // JOSM certificate found, return
446 Logging.debug(tr("JOSM localhost certificate found in {0} keystore: {1}", WINDOWS_ROOT, alias));
447 return false;
448 }
449 } catch (ArrayIndexOutOfBoundsException e) {
450 // catch error of JDK-8172244 as bug seems to not be fixed anytime soon
451 Logging.log(Logging.LEVEL_ERROR, "JDK-8172244 occurred. Abort HTTPS setup", e);
452 return false;
453 }
454 if (!GraphicsEnvironment.isHeadless()) {
455 // JOSM certificate not found, warn user
456 StringBuilder message = new StringBuilder("<html>");
457 message.append(tr("Remote Control is configured to provide HTTPS support.<br>"+
458 "This requires to add a custom certificate generated by JOSM to the Windows Root CA store.<br><br>"+
459 "You are now going to be prompted by Windows to confirm this operation.<br>"+
460 "To enable proper HTTPS support, <b>please click Yes</b> in next dialog.<br><br>"+
461 "If unsure, you can also click No then disable HTTPS support in Remote Control preferences."))
462 .append("</html>");
463 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), message.toString(),
464 tr("HTTPS support in Remote Control"), JOptionPane.INFORMATION_MESSAGE);
465 }
466 // install it to Windows-ROOT keystore, used by IE, Chrome and Safari, but not by Firefox
467 Logging.info(tr("Adding JOSM localhost certificate to {0} keystore", WINDOWS_ROOT));
468 ks.setEntry(entryAlias, trustedCert, null);
469 return true;
470 }
471
472 @Override
473 public X509Certificate getX509Certificate(NativeCertAmend certAmend)
474 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
475 // Get Windows Trust Root Store
476 KeyStore ks = getRootKeystore();
477 // Search by alias (fast)
478 Certificate result = ks.getCertificate(certAmend.getWinAlias());
479 if (result == null) {
480 // Make a web request to target site to force Windows to update if needed its trust root store from its certificate trust list
481 // A better, but a lot more complex method might be to get certificate list from Windows Registry with PowerShell
482 // using (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\AutoUpdate').EncodedCtl)
483 // then decode it using CertUtil -dump or calling CertCreateCTLContext API using JNI, and finally find and decode the certificate
484 Logging.trace(webRequest(certAmend.getWebSite()));
485 // Reload Windows Trust Root Store and search again by alias (fast)
486 ks = getRootKeystore();
487 result = ks.getCertificate(certAmend.getWinAlias());
488 }
489 if (result instanceof X509Certificate) {
490 return (X509Certificate) result;
491 }
492 // If not found, search by SHA-256 (slower)
493 MessageDigest md = MessageDigest.getInstance("SHA-256");
494 for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
495 result = ks.getCertificate(aliases.nextElement());
496 if (result instanceof X509Certificate
497 && certAmend.getSha256().equalsIgnoreCase(Utils.toHexString(md.digest(result.getEncoded())))) {
498 return (X509Certificate) result;
499 }
500 }
501 // Not found
502 return null;
503 }
504
505 @Override
506 public File getDefaultCacheDirectory() {
507 String p = getSystemEnv("LOCALAPPDATA");
508 if (p == null || p.isEmpty()) {
509 // Fallback for Windows OS earlier than Windows Vista, where the variable is not defined
510 p = getSystemEnv("APPDATA");
511 }
512 return new File(new File(p, Preferences.getJOSMDirectoryBaseName()), "cache");
513 }
514
515 @Override
516 public File getDefaultPrefDirectory() {
517 return new File(getSystemEnv("APPDATA"), Preferences.getJOSMDirectoryBaseName());
518 }
519
520 @Override
521 public File getDefaultUserDataDirectory() {
522 // Use preferences directory by default
523 return Config.getDirs().getPreferencesDirectory(false);
524 }
525
526 /**
527 * <p>Add more fallback fonts to the Java runtime, in order to get
528 * support for more scripts.</p>
529 *
530 * <p>The font configuration in Java doesn't include some Indic scripts,
531 * even though MS Windows ships with fonts that cover these unicode ranges.</p>
532 *
533 * <p>To fix this, the fontconfig.properties template is copied to the JOSM
534 * cache folder. Then, the additional entries are added to the font
535 * configuration. Finally the system property "sun.awt.fontconfig" is set
536 * to the customized fontconfig.properties file.</p>
537 *
538 * <p>This is a crude hack, but better than no font display at all for these languages.
539 * There is no guarantee, that the template file
540 * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default
541 * configuration (which is in a binary format).
542 * Furthermore, the system property "sun.awt.fontconfig" is undocumented and
543 * may no longer work in future versions of Java.</p>
544 *
545 * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p>
546 *
547 * @param templateFileName file name of the fontconfig.properties template file
548 */
549 protected void extendFontconfig(String templateFileName) {
550 String customFontconfigFile = Config.getPref().get("fontconfig.properties", null);
551 if (customFontconfigFile != null) {
552 Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile);
553 return;
554 }
555 if (!Config.getPref().getBoolean("font.extended-unicode", true))
556 return;
557
558 String javaLibPath = getSystemProperty("java.home") + File.separator + "lib";
559 Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName);
560 String templatePath = templateFile.toString();
561 if (templatePath.startsWith("null") || !Files.isReadable(templateFile)) {
562 Logging.warn("extended font config - unable to find font config template file {0}", templatePath);
563 return;
564 }
565 try (InputStream fis = Files.newInputStream(templateFile)) {
566 Properties props = new Properties();
567 props.load(fis);
568 byte[] content = Files.readAllBytes(templateFile);
569 File cachePath = Config.getDirs().getCacheDirectory(true);
570 Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties");
571 OutputStream os = Files.newOutputStream(fontconfigFile);
572 os.write(content);
573 try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
574 Collection<FontEntry> extrasPref = StructUtils.getListOfStructs(Config.getPref(),
575 "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class);
576 Collection<FontEntry> extras = new ArrayList<>();
577 w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n");
578 List<String> allCharSubsets = new ArrayList<>();
579 for (FontEntry entry: extrasPref) {
580 Collection<String> fontsAvail = getInstalledFonts();
581 if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) {
582 if (!allCharSubsets.contains(entry.charset)) {
583 allCharSubsets.add(entry.charset);
584 extras.add(entry);
585 } else {
586 Logging.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''",
587 entry.charset, entry.name);
588 }
589 } else {
590 Logging.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name);
591 }
592 }
593 for (FontEntry entry: extras) {
594 allCharSubsets.add(entry.charset);
595 if ("".equals(entry.name)) {
596 continue;
597 }
598 String key = "allfonts." + entry.charset;
599 String value = entry.name;
600 String prevValue = props.getProperty(key);
601 if (prevValue != null && !prevValue.equals(value)) {
602 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
603 }
604 w.append(key + '=' + value + '\n');
605 }
606 w.append('\n');
607 for (FontEntry entry: extras) {
608 if ("".equals(entry.name) || "".equals(entry.file)) {
609 continue;
610 }
611 String key = "filename." + entry.name.replace(' ', '_');
612 String value = entry.file;
613 String prevValue = props.getProperty(key);
614 if (prevValue != null && !prevValue.equals(value)) {
615 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
616 }
617 w.append(key + '=' + value + '\n');
618 }
619 w.append('\n');
620 String fallback = props.getProperty("sequence.fallback");
621 if (fallback != null) {
622 w.append("sequence.fallback=" + fallback + ',' + Utils.join(",", allCharSubsets) + '\n');
623 } else {
624 w.append("sequence.fallback=" + Utils.join(",", allCharSubsets) + '\n');
625 }
626 }
627 Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString());
628 } catch (IOException | InvalidPathException ex) {
629 Logging.error(ex);
630 }
631 }
632
633 /**
634 * Get a list of fonts that are installed on the system.
635 *
636 * Must be done without triggering the Java Font initialization.
637 * (See {@link #extendFontconfig(java.lang.String)}, have to set system
638 * property first, which is then read by sun.awt.FontConfiguration upon initialization.)
639 *
640 * @return list of file names
641 */
642 protected Collection<String> getInstalledFonts() {
643 // Cannot use GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()
644 // because we have to set the system property before Java initializes its fonts.
645 // Use more low-level method to find the installed fonts.
646 List<String> fontsAvail = new ArrayList<>();
647 Path fontPath = FileSystems.getDefault().getPath(getSystemEnv("SYSTEMROOT"), "Fonts");
648 try (DirectoryStream<Path> ds = Files.newDirectoryStream(fontPath)) {
649 for (Path p : ds) {
650 Path filename = p.getFileName();
651 if (filename != null) {
652 fontsAvail.add(filename.toString().toUpperCase(Locale.ENGLISH));
653 }
654 }
655 fontsAvail.add(""); // for devanagari
656 } catch (IOException | DirectoryIteratorException ex) {
657 Logging.log(Logging.LEVEL_ERROR, ex);
658 Logging.warn("extended font config - failed to load available Fonts");
659 fontsAvail = null;
660 }
661 return fontsAvail;
662 }
663
664 /**
665 * Get default list of additional fonts to add to the configuration.
666 *
667 * Java will choose thee first font in the list that can render a certain character.
668 *
669 * @return list of FontEntry objects
670 */
671 protected Collection<FontEntry> getAdditionalFonts() {
672 Collection<FontEntry> def = new ArrayList<>(33);
673 def.add(new FontEntry("devanagari", "", "")); // just include in fallback list font already defined in template
674
675 // Windows scripts: https://msdn.microsoft.com/en-us/goglobal/bb688099.aspx
676 // IE default fonts: https://msdn.microsoft.com/en-us/library/ie/dn467844(v=vs.85).aspx
677
678 // Windows 10 and later
679 def.add(new FontEntry("historic", "Segoe UI Historic", "SEGUIHIS.TTF")); // historic charsets
680
681 // Windows 8/8.1 and later
682 def.add(new FontEntry("javanese", "Javanese Text", "JAVATEXT.TTF")); // ISO 639: jv
683 def.add(new FontEntry("leelawadee", "Leelawadee", "LEELAWAD.TTF")); // ISO 639: bug
684 def.add(new FontEntry("malgun", "Malgun Gothic", "MALGUN.TTF")); // ISO 639: ko
685 def.add(new FontEntry("myanmar", "Myanmar Text", "MMRTEXT.TTF")); // ISO 639: my
686 def.add(new FontEntry("nirmala", "Nirmala UI", "NIRMALA.TTF")); // ISO 639: sat,srb
687 def.add(new FontEntry("segoeui", "Segoe UI", "SEGOEUI.TTF")); // ISO 639: lis
688 def.add(new FontEntry("emoji", "Segoe UI Emoji", "SEGUIEMJ.TTF")); // emoji symbol characters
689
690 // Windows 7 and later
691 def.add(new FontEntry("nko_tifinagh_vai_osmanya", "Ebrima", "EBRIMA.TTF")); // ISO 639: ber. Nko only since Win 8
692 def.add(new FontEntry("khmer1", "Khmer UI", "KHMERUI.TTF")); // ISO 639: km
693 def.add(new FontEntry("lao1", "Lao UI", "LAOUI.TTF")); // ISO 639: lo
694 def.add(new FontEntry("tai_le", "Microsoft Tai Le", "TAILE.TTF")); // ISO 639: khb
695 def.add(new FontEntry("new_tai_lue", "Microsoft New Tai Lue", "NTHAILU.TTF")); // ISO 639: khb
696
697 // Windows Vista and later:
698 def.add(new FontEntry("ethiopic", "Nyala", "NYALA.TTF")); // ISO 639: am,gez,ti
699 def.add(new FontEntry("tibetan", "Microsoft Himalaya", "HIMALAYA.TTF")); // ISO 639: bo,dz
700 def.add(new FontEntry("cherokee", "Plantagenet Cherokee", "PLANTC.TTF")); // ISO 639: chr
701 def.add(new FontEntry("unified_canadian", "Euphemia", "EUPHEMIA.TTF")); // ISO 639: cr,in
702 def.add(new FontEntry("khmer2", "DaunPenh", "DAUNPENH.TTF")); // ISO 639: km
703 def.add(new FontEntry("khmer3", "MoolBoran", "MOOLBOR.TTF")); // ISO 639: km
704 def.add(new FontEntry("lao_thai", "DokChampa", "DOKCHAMP.TTF")); // ISO 639: lo
705 def.add(new FontEntry("mongolian", "Mongolian Baiti", "MONBAITI.TTF")); // ISO 639: mn
706 def.add(new FontEntry("oriya", "Kalinga", "KALINGA.TTF")); // ISO 639: or
707 def.add(new FontEntry("sinhala", "Iskoola Pota", "ISKPOTA.TTF")); // ISO 639: si
708 def.add(new FontEntry("yi", "Yi Baiti", "MSYI.TTF")); // ISO 639: ii
709
710 // Windows XP and later
711 def.add(new FontEntry("gujarati", "Shruti", "SHRUTI.TTF"));
712 def.add(new FontEntry("kannada", "Tunga", "TUNGA.TTF"));
713 def.add(new FontEntry("gurmukhi", "Raavi", "RAAVI.TTF"));
714 def.add(new FontEntry("telugu", "Gautami", "GAUTAMI.TTF"));
715 def.add(new FontEntry("bengali", "Vrinda", "VRINDA.TTF")); // since XP SP2
716 def.add(new FontEntry("syriac", "Estrangelo Edessa", "ESTRE.TTF")); // ISO 639: arc
717 def.add(new FontEntry("thaana", "MV Boli", "MVBOLI.TTF")); // ISO 639: dv
718 def.add(new FontEntry("malayalam", "Kartika", "KARTIKA.TTF")); // ISO 639: ml; since XP SP2
719
720 // Windows 2000 and later
721 def.add(new FontEntry("tamil", "Latha", "LATHA.TTF"));
722
723 // Comes with MS Office & Outlook 2000. Good unicode coverage, so add if available.
724 def.add(new FontEntry("arialuni", "Arial Unicode MS", "ARIALUNI.TTF"));
725
726 return def;
727 }
728
729 /**
730 * Determines if the .NET framework 4.5 (or later) is installed.
731 * Windows 7 ships by default with an older version.
732 * @return {@code true} if the .NET framework 4.5 (or later) is installed.
733 * @since 13463
734 */
735 public static boolean isDotNet45Installed() {
736 try {
737 // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d
738 // "The existence of the Release DWORD indicates that the .NET Framework 4.5 or later has been installed"
739 // Great, but our WinRegistry only handles REG_SZ type, so we have to check the Version key
740 String version = WinRegistry.readString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", "Version");
741 if (version != null) {
742 Matcher m = MS_VERSION_PATTERN.matcher(version);
743 if (m.matches()) {
744 int maj = Integer.parseInt(m.group(1));
745 int min = Integer.parseInt(m.group(2));
746 return (maj == 4 && min >= 5) || maj > 4;
747 }
748 }
749 } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
750 Logging.error(e);
751 }
752 return false;
753 }
754
755 /**
756 * Returns the major version number of PowerShell.
757 * @return the major version number of PowerShell. -1 in case of error
758 * @since 13465
759 */
760 public static int getPowerShellVersion() {
761 try {
762 String version = WinRegistry.readString(
763 HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Powershell\\3\\PowershellEngine", "PowershellVersion");
764 if (version != null) {
765 Matcher m = MS_VERSION_PATTERN.matcher(version);
766 if (m.matches()) {
767 return Integer.parseInt(m.group(1));
768 }
769 }
770 } catch (NumberFormatException | IllegalAccessException | InvocationTargetException e) {
771 Logging.error(e);
772 }
773 return -1;
774 }
775
776 /**
777 * Performs a web request using Windows CryptoAPI (through PowerShell).
778 * This is useful to ensure Windows trust store will contain a specific root CA.
779 * @param uri the web URI to request
780 * @return HTTP response from the given URI
781 * @throws IOException if any I/O error occurs
782 * @since 13458
783 */
784 public static String webRequest(String uri) throws IOException {
785 // With PS 6.0 (not yet released in Windows) we could simply use:
786 // Invoke-WebRequest -SSlProtocol Tsl12 $uri
787 // .NET framework < 4.5 does not support TLS 1.2 (https://stackoverflow.com/a/43240673/2257172)
788 if (isDotNet45Installed() && getPowerShellVersion() >= 3) {
789 try {
790 // The following works with PS 3.0 (Windows 8+), https://stackoverflow.com/a/41618979/2257172
791 return Utils.execOutput(Arrays.asList("powershell", "-Command",
792 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;"+
793 "[System.Net.WebRequest]::Create('"+uri+"').GetResponse()"
794 ), 5, TimeUnit.SECONDS);
795 } catch (ExecutionException | InterruptedException e) {
796 Logging.error(e);
797 }
798 }
799 return null;
800 }
801
802 @Override
803 public File resolveFileLink(File file) {
804 if (file.getName().endsWith(".lnk")) {
805 try {
806 return new File(new WindowsShortcut(file).getRealFilename());
807 } catch (IOException | ParseException e) {
808 Logging.error(e);
809 }
810 }
811 return file;
812 }
813
814 @Override
815 public Collection<String> getPossiblePreferenceDirs() {
816 Set<String> locations = new HashSet<>();
817 String appdata = getSystemEnv("APPDATA");
818 if (appdata != null && getSystemEnv("ALLUSERSPROFILE") != null
819 && appdata.lastIndexOf(File.separator) != -1) {
820 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
821 locations.add(new File(new File(getSystemEnv("ALLUSERSPROFILE"), appdata), "JOSM").getPath());
822 }
823 return locations;
824 }
825}
Note: See TracBrowser for help on using the repository browser.