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

Last change on this file since 18985 was 18985, checked in by taylor.smock, 12 months ago

Fix #23355: Sanity check JVM arguments on startup

See #17858: JOSM will no longer continue running if the user is on an unsupported
Java version (for this commit, older than Java 11; message indicates Java 17).
This does update the link for Azul from Java 17 to Java 21 as well.

In order to (hopefully) reduce confusion, the webstart and Java update nags will
also be reset in the event that JOSM will exit due to old Java versions. This is
mostly so that users will get the messages to update to OpenWebstart or the
appropriate Java link for their platform and architecture.

Additionally, this will (hopefully) reduce the number of tickets we have to close
due to missing JVM arguments by informing users of the missing arguments at startup.

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