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

Last change on this file since 18580 was 18580, checked in by taylor.smock, 3 years ago

See #17858: start linking to Java 17 for Java updates.

The link for the Java download page now goes to azul.com, and attempts to pre-fill as
much of the download form as possible.

This also adds a method to warn users running Java 10 or earlier that their version
of Java will soon be unsupported by JOSM. This only affects users using Java 10 or
earlier if they are not running JOSM using WebStart.

  • Property svn:eol-style set to native
File size: 38.8 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.io.BufferedWriter;
35import java.io.File;
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.io.OutputStreamWriter;
40import java.io.Writer;
41import java.lang.reflect.InvocationTargetException;
42import java.net.URISyntaxException;
43import java.nio.charset.StandardCharsets;
44import java.nio.file.DirectoryIteratorException;
45import java.nio.file.DirectoryStream;
46import java.nio.file.FileSystems;
47import java.nio.file.Files;
48import java.nio.file.InvalidPathException;
49import java.nio.file.Path;
50import java.security.KeyStore;
51import java.security.KeyStoreException;
52import java.security.MessageDigest;
53import java.security.NoSuchAlgorithmException;
54import java.security.cert.Certificate;
55import java.security.cert.CertificateEncodingException;
56import java.security.cert.CertificateException;
57import java.security.cert.X509Certificate;
58import java.text.ParseException;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.Collection;
62import java.util.Enumeration;
63import java.util.HashSet;
64import java.util.List;
65import java.util.Locale;
66import java.util.Properties;
67import java.util.Set;
68import java.util.concurrent.ExecutionException;
69import java.util.concurrent.TimeUnit;
70import java.util.regex.Matcher;
71import java.util.regex.Pattern;
72
73import org.openstreetmap.josm.data.Preferences;
74import org.openstreetmap.josm.data.StructUtils;
75import org.openstreetmap.josm.data.StructUtils.StructEntry;
76import org.openstreetmap.josm.data.StructUtils.WriteExplicitly;
77import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend;
78import org.openstreetmap.josm.io.NetworkManager;
79import org.openstreetmap.josm.io.OnlineResource;
80import org.openstreetmap.josm.spi.preferences.Config;
81
82/**
83 * {@code PlatformHook} implementation for Microsoft Windows systems.
84 * @since 1023
85 */
86public class PlatformHookWindows implements PlatformHook {
87
88 /**
89 * Pattern of Microsoft .NET and Powershell version numbers in registry.
90 */
91 private static final Pattern MS_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+.*)?");
92
93 /**
94 * Simple data class to hold information about a font.
95 *
96 * Used for fontconfig.properties files.
97 */
98 public static class FontEntry {
99 /**
100 * The character subset. Basically a free identifier, but should be unique.
101 */
102 @StructEntry
103 public String charset;
104
105 /**
106 * Platform font name.
107 */
108 @StructEntry
109 @WriteExplicitly
110 public String name = "";
111
112 /**
113 * File name.
114 */
115 @StructEntry
116 @WriteExplicitly
117 public String file = "";
118
119 /**
120 * Constructs a new {@code FontEntry}.
121 */
122 public FontEntry() {
123 // Default constructor needed for construction by reflection
124 }
125
126 /**
127 * Constructs a new {@code FontEntry}.
128 * @param charset The character subset. Basically a free identifier, but should be unique
129 * @param name Platform font name
130 * @param file File name
131 */
132 public FontEntry(String charset, String name, String file) {
133 this.charset = charset;
134 this.name = name;
135 this.file = file;
136 }
137 }
138
139 private static final String WINDOWS_ROOT = "Windows-ROOT";
140
141 private static final String CURRENT_VERSION = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
142
143 private String oSBuildNumber;
144
145 @Override
146 public Platform getPlatform() {
147 return Platform.WINDOWS;
148 }
149
150 @Override
151 public void afterPrefStartupHook() {
152 extendFontconfig("fontconfig.properties.src");
153 }
154
155 @Override
156 public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) {
157 warnSoonToBeUnsupportedJava(javaCallback);
158 checkExpiredJava(javaCallback);
159 checkWebStartMigration(webStartCallback);
160 }
161
162 @Override
163 public void openUrl(String url) throws IOException {
164 if (!url.startsWith("file:/")) {
165 final String customBrowser = Config.getPref().get("browser.windows", "");
166 if (!customBrowser.isEmpty()) {
167 Runtime.getRuntime().exec(new String[]{customBrowser, url});
168 return;
169 }
170 }
171 try {
172 // Desktop API works fine under Windows
173 Desktop.getDesktop().browse(Utils.urlToURI(url));
174 } catch (IOException | URISyntaxException e) {
175 Logging.log(Logging.LEVEL_WARN, "Desktop class failed. Platform dependent fall back for open url in browser.", e);
176 Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url});
177 }
178 }
179
180 @Override
181 public void initSystemShortcuts() {
182 // CHECKSTYLE.OFF: LineLength
183 //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK);
184 Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results
185
186 // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts
187
188 // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all
189
190 // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page
191 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic();
192
193 // Ease of Access keyboard shortcuts
194 Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off
195 Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off
196 //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?)
197
198 // General keyboard shortcuts
199 //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0); // Display Help
200 Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK); // Copy the selected item
201 Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK); // Cut the selected item
202 Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK); // Paste the selected item
203 Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK); // Undo an action
204 Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK); // Redo an action
205 //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0); // Delete the selected item and move it to the Recycle Bin
206 //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK); // Delete the selected item without moving it to the Recycle Bin first
207 //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0); // Rename the selected item
208 Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next word
209 Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous word
210 Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next paragraph
211 Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous paragraph
212 //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
213 //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
214 //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
215 //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text
216 //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
217 //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
218 //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
219 //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
220 //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 ?)
221 //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 ?)
222 //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 ?)
223 //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 ?)
224 Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK); // Select all items in a document or window
225 //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0); // Search for a file or folder
226 Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic(); // Display properties for the selected item
227 Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program
228 Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic(); // Open the shortcut menu for the active window
229 //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)
230 Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic(); // Switch between open items
231 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
232 //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 ?)
233 //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 ?)
234 Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic(); // Cycle through items in the order in which they were opened
235 //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0); // Cycle through screen elements in a window or on the desktop
236 //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0); // Display the address bar list in Windows Explorer
237 Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK); // Display the shortcut menu for the selected item
238 Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu
239 //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0); // Activate the menu bar in the active program
240 //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0); // Open the next menu to the right, or open a submenu
241 //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0); // Open the next menu to the left, or close a submenu
242 //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0); // Refresh the active window
243 //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK); // View the folder one level up in Windows Explorer
244 //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0); // Cancel the current task
245 Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager
246 Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic(); // Switch the input language when multiple input languages are enabled
247 Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic(); // Switch the keyboard layout when multiple keyboard layouts are enabled
248 //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear)
249 // CHECKSTYLE.ON: LineLength
250 }
251
252 @Override
253 public String getDefaultStyle() {
254 return "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
255 }
256
257 @Override
258 public boolean rename(File from, File to) {
259 if (to.exists())
260 Utils.deleteFile(to);
261 return from.renameTo(to);
262 }
263
264 @Override
265 public String getOSDescription() {
266 return Utils.strip(getSystemProperty("os.name")) + ' ' +
267 ((getSystemEnv("ProgramFiles(x86)") == null) ? "32" : "64") + "-Bit";
268 }
269
270 /**
271 * Returns the Windows product name from registry (example: "Windows 10 Pro")
272 * @return the Windows product name from registry
273 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
274 * @throws InvocationTargetException if the underlying method throws an exception
275 * @since 12744
276 */
277 public static String getProductName() throws IllegalAccessException, InvocationTargetException {
278 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ProductName");
279 }
280
281 /**
282 * Returns the Windows release identifier from registry (example: "1703")
283 * @return the Windows release identifier from registry
284 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
285 * @throws InvocationTargetException if the underlying method throws an exception
286 * @since 12744
287 */
288 public static String getReleaseId() throws IllegalAccessException, InvocationTargetException {
289 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ReleaseId");
290 }
291
292 /**
293 * Returns the Windows current build number from registry (example: "15063")
294 * @return the Windows current build number from registry
295 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
296 * @throws InvocationTargetException if the underlying method throws an exception
297 * @since 12744
298 */
299 public static String getCurrentBuild() throws IllegalAccessException, InvocationTargetException {
300 return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "CurrentBuild");
301 }
302
303 private static String buildOSBuildNumber() {
304 StringBuilder sb = new StringBuilder();
305 try {
306 sb.append(getProductName());
307 String releaseId = getReleaseId();
308 if (releaseId != null) {
309 sb.append(' ').append(releaseId);
310 }
311 sb.append(" (").append(getCurrentBuild()).append(')');
312 } catch (ReflectiveOperationException | JosmRuntimeException | NoClassDefFoundError e) {
313 Logging.log(Logging.LEVEL_ERROR, "Unable to get Windows build number", e);
314 Logging.debug(e);
315 }
316 return sb.toString();
317 }
318
319 @Override
320 public String getOSBuildNumber() {
321 if (oSBuildNumber == null) {
322 oSBuildNumber = buildOSBuildNumber();
323 }
324 return oSBuildNumber;
325 }
326
327 /**
328 * Loads Windows-ROOT keystore.
329 * @return Windows-ROOT keystore
330 * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found
331 * @throws CertificateException if any of the certificates in the keystore could not be loaded
332 * @throws IOException if there is an I/O or format problem with the keystore data, if a password is required but not given
333 * @throws KeyStoreException if no Provider supports a KeyStore implementation for the type "Windows-ROOT"
334 * @since 7343
335 */
336 public static KeyStore getRootKeystore() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
337 KeyStore ks = KeyStore.getInstance(WINDOWS_ROOT);
338 ks.load(null, null);
339 return ks;
340 }
341
342 @Override
343 public X509Certificate getX509Certificate(NativeCertAmend certAmend)
344 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
345 MessageDigest md = MessageDigest.getInstance("SHA-256");
346 // Get Windows Trust Root Store
347 KeyStore ks = getRootKeystore();
348 // Search by alias (fast)
349 for (String winAlias : certAmend.getNativeAliases()) {
350 Certificate result = ks.getCertificate(winAlias);
351 // Check for SHA-256 signature, as sometimes Microsoft can ship several certificates with the same alias, for example:
352 // AC RAIZ FNMT-RCM: EBC5570C29018C4D67B1AA127BAF12F703B4611EBC17B7DAB5573894179B93FA (SHA256)
353 // AC RAIZ FNMT-RCM: 4D9EBB28825C9643AB15D54E5F9614F13CB3E95DE3CF4EAC971301F320F9226E (SHA1)
354 if (!sha256matches(result, certAmend, md)) {
355 Logging.trace("Ignoring {0} as SHA-256 signature does not match", result);
356 result = null;
357 }
358 if (result == null && !NetworkManager.isOffline(OnlineResource.CERTIFICATES)) {
359 // Make a web request to target site to force Windows to update if needed its trust root store from its certificate trust list
360 // A better, but a lot more complex method might be to get certificate list from Windows Registry with PowerShell
361 // using (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\SystemCertificates\\AuthRoot\\AutoUpdate').EncodedCtl)
362 // then decode it using CertUtil -dump or calling CertCreateCTLContext API using JNI, and finally find and decode the certificate
363 Logging.trace(webRequest(certAmend.getWebSite()));
364 // Reload Windows Trust Root Store and search again by alias (fast)
365 ks = getRootKeystore();
366 result = ks.getCertificate(winAlias);
367 }
368 if (result instanceof X509Certificate) {
369 return (X509Certificate) result;
370 }
371 }
372 // If not found, search by SHA-256 (slower)
373 for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements();) {
374 String alias = aliases.nextElement();
375 Certificate result = ks.getCertificate(alias);
376 if (sha256matches(result, certAmend, md)) {
377 Logging.warn("Certificate not found for alias ''{0}'' but found for alias ''{1}''", certAmend.getNativeAliases(), alias);
378 return (X509Certificate) result;
379 }
380 }
381 // Not found
382 return null;
383 }
384
385 private static boolean sha256matches(Certificate result, NativeCertAmend certAmend, MessageDigest md) throws CertificateEncodingException {
386 return result instanceof X509Certificate
387 && certAmend.getSha256().equalsIgnoreCase(Utils.toHexString(md.digest(result.getEncoded())));
388 }
389
390 @Override
391 public File getDefaultCacheDirectory() {
392 String p = getSystemEnv("LOCALAPPDATA");
393 if (Utils.isEmpty(p)) {
394 // Fallback for Windows OS earlier than Windows Vista, where the variable is not defined
395 p = getSystemEnv("APPDATA");
396 }
397 return new File(new File(p, Preferences.getJOSMDirectoryBaseName()), "cache");
398 }
399
400 @Override
401 public File getDefaultPrefDirectory() {
402 return new File(getSystemEnv("APPDATA"), Preferences.getJOSMDirectoryBaseName());
403 }
404
405 @Override
406 public File getDefaultUserDataDirectory() {
407 // Use preferences directory by default
408 return Config.getDirs().getPreferencesDirectory(false);
409 }
410
411 /**
412 * <p>Add more fallback fonts to the Java runtime, in order to get
413 * support for more scripts.</p>
414 *
415 * <p>The font configuration in Java doesn't include some Indic scripts,
416 * even though MS Windows ships with fonts that cover these unicode ranges.</p>
417 *
418 * <p>To fix this, the fontconfig.properties template is copied to the JOSM
419 * cache folder. Then, the additional entries are added to the font
420 * configuration. Finally the system property "sun.awt.fontconfig" is set
421 * to the customized fontconfig.properties file.</p>
422 *
423 * <p>This is a crude hack, but better than no font display at all for these languages.
424 * There is no guarantee, that the template file
425 * ($JAVA_HOME/lib/fontconfig.properties.src) matches the default
426 * configuration (which is in a binary format).
427 * Furthermore, the system property "sun.awt.fontconfig" is undocumented and
428 * may no longer work in future versions of Java.</p>
429 *
430 * <p>Related Java bug: <a href="https://bugs.openjdk.java.net/browse/JDK-8008572">JDK-8008572</a></p>
431 *
432 * @param templateFileName file name of the fontconfig.properties template file
433 */
434 protected void extendFontconfig(String templateFileName) {
435 String customFontconfigFile = Config.getPref().get("fontconfig.properties", null);
436 if (customFontconfigFile != null) {
437 Utils.updateSystemProperty("sun.awt.fontconfig", customFontconfigFile);
438 return;
439 }
440 if (!Config.getPref().getBoolean("font.extended-unicode", true))
441 return;
442
443 String javaLibPath = getSystemProperty("java.home") + File.separator + "lib";
444 Path templateFile = FileSystems.getDefault().getPath(javaLibPath, templateFileName);
445 String templatePath = templateFile.toString();
446 if (templatePath.startsWith("null") || !Files.isReadable(templateFile)) {
447 Logging.warn("extended font config - unable to find font config template file {0}", templatePath);
448 return;
449 }
450 try (InputStream fis = Files.newInputStream(templateFile)) {
451 Properties props = new Properties();
452 props.load(fis);
453 byte[] content = Files.readAllBytes(templateFile);
454 File cachePath = Config.getDirs().getCacheDirectory(true);
455 Path fontconfigFile = cachePath.toPath().resolve("fontconfig.properties");
456 OutputStream os = Files.newOutputStream(fontconfigFile); // NOPMD
457 os.write(content);
458 try (Writer w = new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
459 Collection<FontEntry> extrasPref = StructUtils.getListOfStructs(Config.getPref(),
460 "font.extended-unicode.extra-items", getAdditionalFonts(), FontEntry.class);
461 Collection<FontEntry> extras = new ArrayList<>();
462 w.append("\n\n# Added by JOSM to extend unicode coverage of Java font support:\n\n");
463 List<String> allCharSubsets = new ArrayList<>();
464 for (FontEntry entry: extrasPref) {
465 Collection<String> fontsAvail = getInstalledFonts();
466 if (fontsAvail != null && fontsAvail.contains(entry.file.toUpperCase(Locale.ENGLISH))) {
467 if (!allCharSubsets.contains(entry.charset)) {
468 allCharSubsets.add(entry.charset);
469 extras.add(entry);
470 } else {
471 Logging.trace("extended font config - already registered font for charset ''{0}'' - skipping ''{1}''",
472 entry.charset, entry.name);
473 }
474 } else {
475 Logging.trace("extended font config - Font ''{0}'' not found on system - skipping", entry.name);
476 }
477 }
478 for (FontEntry entry: extras) {
479 allCharSubsets.add(entry.charset);
480 if ("".equals(entry.name)) {
481 continue;
482 }
483 String key = "allfonts." + entry.charset;
484 String value = entry.name;
485 String prevValue = props.getProperty(key);
486 if (prevValue != null && !prevValue.equals(value)) {
487 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
488 }
489 w.append(key + '=' + value + '\n');
490 }
491 w.append('\n');
492 for (FontEntry entry: extras) {
493 if ("".equals(entry.name) || "".equals(entry.file)) {
494 continue;
495 }
496 String key = "filename." + entry.name.replace(' ', '_');
497 String value = entry.file;
498 String prevValue = props.getProperty(key);
499 if (prevValue != null && !prevValue.equals(value)) {
500 Logging.warn("extended font config - overriding ''{0}={1}'' with ''{2}''", key, prevValue, value);
501 }
502 w.append(key + '=' + value + '\n');
503 }
504 w.append('\n');
505 w.append("sequence.fallback=");
506 String fallback = props.getProperty("sequence.fallback");
507 if (fallback != null) {
508 w.append(fallback).append(",");
509 }
510 w.append(String.join(",", allCharSubsets)).append("\n");
511 }
512 Utils.updateSystemProperty("sun.awt.fontconfig", fontconfigFile.toString());
513 } catch (IOException | InvalidPathException ex) {
514 Logging.error(ex);
515 }
516 }
517
518 /**
519 * Get a list of fonts that are installed on the system.
520 *
521 * Must be done without triggering the Java Font initialization.
522 * (See {@link #extendFontconfig(java.lang.String)}, have to set system
523 * property first, which is then read by sun.awt.FontConfiguration upon initialization.)
524 *
525 * @return list of file names
526 */
527 protected Collection<String> getInstalledFonts() {
528 // Cannot use GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()
529 // because we have to set the system property before Java initializes its fonts.
530 // Use more low-level method to find the installed fonts.
531 List<String> fontsAvail = new ArrayList<>();
532 String systemRoot = getSystemEnv("SYSTEMROOT");
533 if (systemRoot == null) {
534 return fontsAvail;
535 }
536 Path fontPath = FileSystems.getDefault().getPath(systemRoot, "Fonts");
537 try (DirectoryStream<Path> ds = Files.newDirectoryStream(fontPath)) {
538 for (Path p : ds) {
539 Path filename = p.getFileName();
540 if (filename != null) {
541 fontsAvail.add(filename.toString().toUpperCase(Locale.ENGLISH));
542 }
543 }
544 fontsAvail.add(""); // for devanagari
545 } catch (IOException | DirectoryIteratorException ex) {
546 Logging.log(Logging.LEVEL_ERROR, ex);
547 Logging.warn("extended font config - failed to load available Fonts");
548 fontsAvail = null;
549 }
550 return fontsAvail;
551 }
552
553 /**
554 * Get default list of additional fonts to add to the configuration.
555 *
556 * Java will choose thee first font in the list that can render a certain character.
557 *
558 * @return list of FontEntry objects
559 */
560 protected Collection<FontEntry> getAdditionalFonts() {
561 Collection<FontEntry> def = new ArrayList<>(33);
562 def.add(new FontEntry("devanagari", "", "")); // just include in fallback list font already defined in template
563
564 // Windows scripts: https://msdn.microsoft.com/en-us/goglobal/bb688099.aspx
565 // IE default fonts: https://msdn.microsoft.com/en-us/library/ie/dn467844(v=vs.85).aspx
566
567 // Windows 10 and later
568 def.add(new FontEntry("historic", "Segoe UI Historic", "SEGUIHIS.TTF")); // historic charsets
569
570 // Windows 8/8.1 and later
571 def.add(new FontEntry("javanese", "Javanese Text", "JAVATEXT.TTF")); // ISO 639: jv
572 def.add(new FontEntry("leelawadee", "Leelawadee", "LEELAWAD.TTF")); // ISO 639: bug
573 def.add(new FontEntry("malgun", "Malgun Gothic", "MALGUN.TTF")); // ISO 639: ko
574 def.add(new FontEntry("myanmar", "Myanmar Text", "MMRTEXT.TTF")); // ISO 639: my
575 def.add(new FontEntry("nirmala", "Nirmala UI", "NIRMALA.TTF")); // ISO 639: sat,srb
576 def.add(new FontEntry("segoeui", "Segoe UI", "SEGOEUI.TTF")); // ISO 639: lis
577 def.add(new FontEntry("emoji", "Segoe UI Emoji", "SEGUIEMJ.TTF")); // emoji symbol characters
578
579 // Windows 7 and later
580 def.add(new FontEntry("nko_tifinagh_vai_osmanya", "Ebrima", "EBRIMA.TTF")); // ISO 639: ber. Nko only since Win 8
581 def.add(new FontEntry("khmer1", "Khmer UI", "KHMERUI.TTF")); // ISO 639: km
582 def.add(new FontEntry("lao1", "Lao UI", "LAOUI.TTF")); // ISO 639: lo
583 def.add(new FontEntry("tai_le", "Microsoft Tai Le", "TAILE.TTF")); // ISO 639: khb
584 def.add(new FontEntry("new_tai_lue", "Microsoft New Tai Lue", "NTHAILU.TTF")); // ISO 639: khb
585
586 // Windows Vista and later:
587 def.add(new FontEntry("ethiopic", "Nyala", "NYALA.TTF")); // ISO 639: am,gez,ti
588 def.add(new FontEntry("tibetan", "Microsoft Himalaya", "HIMALAYA.TTF")); // ISO 639: bo,dz
589 def.add(new FontEntry("cherokee", "Plantagenet Cherokee", "PLANTC.TTF")); // ISO 639: chr
590 def.add(new FontEntry("unified_canadian", "Euphemia", "EUPHEMIA.TTF")); // ISO 639: cr,in
591 def.add(new FontEntry("khmer2", "DaunPenh", "DAUNPENH.TTF")); // ISO 639: km
592 def.add(new FontEntry("khmer3", "MoolBoran", "MOOLBOR.TTF")); // ISO 639: km
593 def.add(new FontEntry("lao_thai", "DokChampa", "DOKCHAMP.TTF")); // ISO 639: lo
594 def.add(new FontEntry("mongolian", "Mongolian Baiti", "MONBAITI.TTF")); // ISO 639: mn
595 def.add(new FontEntry("oriya", "Kalinga", "KALINGA.TTF")); // ISO 639: or
596 def.add(new FontEntry("sinhala", "Iskoola Pota", "ISKPOTA.TTF")); // ISO 639: si
597 def.add(new FontEntry("yi", "Yi Baiti", "MSYI.TTF")); // ISO 639: ii
598
599 // Windows XP and later
600 def.add(new FontEntry("gujarati", "Shruti", "SHRUTI.TTF"));
601 def.add(new FontEntry("kannada", "Tunga", "TUNGA.TTF"));
602 def.add(new FontEntry("gurmukhi", "Raavi", "RAAVI.TTF"));
603 def.add(new FontEntry("telugu", "Gautami", "GAUTAMI.TTF"));
604 def.add(new FontEntry("bengali", "Vrinda", "VRINDA.TTF")); // since XP SP2
605 def.add(new FontEntry("syriac", "Estrangelo Edessa", "ESTRE.TTF")); // ISO 639: arc
606 def.add(new FontEntry("thaana", "MV Boli", "MVBOLI.TTF")); // ISO 639: dv
607 def.add(new FontEntry("malayalam", "Kartika", "KARTIKA.TTF")); // ISO 639: ml; since XP SP2
608
609 // Windows 2000 and later
610 def.add(new FontEntry("tamil", "Latha", "LATHA.TTF"));
611
612 // Comes with MS Office & Outlook 2000. Good unicode coverage, so add if available.
613 def.add(new FontEntry("arialuni", "Arial Unicode MS", "ARIALUNI.TTF"));
614
615 return def;
616 }
617
618 /**
619 * Determines if the .NET framework 4.5 (or later) is installed.
620 * Windows 7 ships by default with an older version.
621 * @return {@code true} if the .NET framework 4.5 (or later) is installed.
622 * @since 13463
623 */
624 public static boolean isDotNet45Installed() {
625 try {
626 // https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d
627 // "The existence of the Release DWORD indicates that the .NET Framework 4.5 or later has been installed"
628 // Great, but our WinRegistry only handles REG_SZ type, so we have to check the Version key
629 String version = WinRegistry.readString(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full", "Version");
630 if (version != null) {
631 Matcher m = MS_VERSION_PATTERN.matcher(version);
632 if (m.matches()) {
633 int maj = Integer.parseInt(m.group(1));
634 int min = Integer.parseInt(m.group(2));
635 return (maj == 4 && min >= 5) || maj > 4;
636 }
637 }
638 } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
639 Logging.error(e);
640 }
641 return false;
642 }
643
644 /**
645 * Returns the major version number of PowerShell.
646 * @return the major version number of PowerShell. -1 in case of error
647 * @since 13465
648 */
649 public static int getPowerShellVersion() {
650 try {
651 String version = WinRegistry.readString(
652 HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Powershell\\3\\PowershellEngine", "PowershellVersion");
653 if (version != null) {
654 Matcher m = MS_VERSION_PATTERN.matcher(version);
655 if (m.matches()) {
656 return Integer.parseInt(m.group(1));
657 }
658 }
659 } catch (NumberFormatException | IllegalAccessException | InvocationTargetException e) {
660 Logging.error(e);
661 }
662 return -1;
663 }
664
665 /**
666 * Performs a web request using Windows CryptoAPI (through PowerShell).
667 * This is useful to ensure Windows trust store will contain a specific root CA.
668 * @param uri the web URI to request
669 * @return HTTP response from the given URI
670 * @throws IOException if any I/O error occurs
671 * @since 13458
672 */
673 public static String webRequest(String uri) throws IOException {
674 // With PS 6.0 (not yet released in Windows) we could simply use:
675 // Invoke-WebRequest -SSlProtocol Tsl12 $uri
676 // .NET framework < 4.5 does not support TLS 1.2 (https://stackoverflow.com/a/43240673/2257172)
677 if (isDotNet45Installed() && getPowerShellVersion() >= 3) {
678 try {
679 // The following works with PS 3.0 (Windows 8+), https://stackoverflow.com/a/41618979/2257172
680 return Utils.execOutput(Arrays.asList("powershell", "-Command",
681 "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;"+
682 "[System.Net.WebRequest]::Create('"+uri+"').GetResponse()"
683 ), 5, TimeUnit.SECONDS);
684 } catch (ExecutionException | InterruptedException e) {
685 Logging.warn("Unable to request certificate of " + uri);
686 Logging.debug(e);
687 }
688 }
689 return null;
690 }
691
692 @Override
693 public File resolveFileLink(File file) {
694 if (file.getName().endsWith(".lnk")) {
695 try {
696 return new File(new WindowsShortcut(file).getRealFilename());
697 } catch (IOException | ParseException e) {
698 Logging.error(e);
699 }
700 }
701 return file;
702 }
703
704 @Override
705 public Collection<String> getPossiblePreferenceDirs() {
706 Set<String> locations = new HashSet<>();
707 String appdata = getSystemEnv("APPDATA");
708 if (appdata != null && getSystemEnv("ALLUSERSPROFILE") != null
709 && appdata.lastIndexOf(File.separator) != -1) {
710 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
711 locations.add(new File(new File(getSystemEnv("ALLUSERSPROFILE"), appdata), "JOSM").getPath());
712 }
713 return locations;
714 }
715}
Note: See TracBrowser for help on using the repository browser.