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

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

fix #17160 - don't open local files with custom browser

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