1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.util.*; 29 import java.util.stream.Collectors; 30 import java.util.stream.Stream; 31 32 public class WindowsHelper { 33 34 static String getBundleName(JPackageCommand cmd) { 35 cmd.verifyIsOfType(PackageType.WINDOWS); 36 return String.format("%s-%s%s", cmd.name(), cmd.version(), 37 cmd.packageType().getSuffix()); 38 } 39 40 static Path getInstallationDirectory(JPackageCommand cmd) { 41 cmd.verifyIsOfType(PackageType.WINDOWS); 42 Path installDir = Path.of( 43 cmd.getArgumentValue("--install-dir", () -> cmd.name())); 44 if (isUserLocalInstall(cmd)) { 45 return USER_LOCAL.resolve(installDir); 46 } 47 return PROGRAM_FILES.resolve(installDir); 48 } 49 50 private static boolean isUserLocalInstall(JPackageCommand cmd) { 51 return cmd.hasArgument("--win-per-user-install"); 52 } 53 54 static class AppVerifier { 55 56 AppVerifier(JPackageCommand cmd) { 57 cmd.verifyIsOfType(PackageType.WINDOWS); 58 this.cmd = cmd; 59 verifyStartMenuShortcut(); 60 verifyDesktopShortcut(); 61 verifyFileAssociationsRegistry(); 62 } 63 64 private void verifyDesktopShortcut() { 65 boolean appInstalled = cmd.appLauncherPath().toFile().exists(); 66 if (cmd.hasArgument("--win-shortcut")) { 67 if (isUserLocalInstall(cmd)) { 68 verifyUserLocalDesktopShortcut(appInstalled); 69 verifySystemDesktopShortcut(false); 70 } else { 71 verifySystemDesktopShortcut(appInstalled); 72 verifyUserLocalDesktopShortcut(false); 73 } 74 } else { 75 verifySystemDesktopShortcut(false); 76 verifyUserLocalDesktopShortcut(false); 77 } 78 } 79 80 private Path desktopShortcutPath() { 81 return Path.of(cmd.name() + ".lnk"); 82 } 83 84 private void verifyShortcut(Path path, boolean exists) { 85 if (exists) { 86 TKit.assertFileExists(path); 87 } else { 88 TKit.assertPathExists(path, false); 89 } 90 } 91 92 private void verifySystemDesktopShortcut(boolean exists) { 93 Path dir = Path.of(queryRegistryValueCache( 94 SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop")); 95 verifyShortcut(dir.resolve(desktopShortcutPath()), exists); 96 } 97 98 private void verifyUserLocalDesktopShortcut(boolean exists) { 99 Path dir = Path.of( 100 queryRegistryValueCache(USER_SHELL_FOLDERS_REGKEY, "Desktop")); 101 verifyShortcut(dir.resolve(desktopShortcutPath()), exists); 102 } 103 104 private void verifyStartMenuShortcut() { 105 boolean appInstalled = cmd.appLauncherPath().toFile().exists(); 106 if (cmd.hasArgument("--win-menu")) { 107 if (isUserLocalInstall(cmd)) { 108 verifyUserLocalStartMenuShortcut(appInstalled); 109 verifySystemStartMenuShortcut(false); 110 } else { 111 verifySystemStartMenuShortcut(appInstalled); 112 verifyUserLocalStartMenuShortcut(false); 113 } 114 } else { 115 verifySystemStartMenuShortcut(false); 116 verifyUserLocalStartMenuShortcut(false); 117 } 118 } 119 120 private Path startMenuShortcutPath() { 121 return Path.of(cmd.getArgumentValue("--win-menu-group", 122 () -> "Unknown"), cmd.name() + ".lnk"); 123 } 124 125 private void verifyStartMenuShortcut(Path shortcutsRoot, boolean exists) { 126 Path shortcutPath = shortcutsRoot.resolve(startMenuShortcutPath()); 127 verifyShortcut(shortcutPath, exists); 128 if (!exists) { 129 TKit.assertPathExists(shortcutPath.getParent(), false); 130 } 131 } 132 133 private void verifySystemStartMenuShortcut(boolean exists) { 134 verifyStartMenuShortcut(Path.of(queryRegistryValueCache( 135 SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs")), exists); 136 137 } 138 139 private void verifyUserLocalStartMenuShortcut(boolean exists) { 140 verifyStartMenuShortcut(Path.of(queryRegistryValueCache( 141 USER_SHELL_FOLDERS_REGKEY, "Programs")), exists); 142 } 143 144 private void verifyFileAssociationsRegistry() { 145 Stream.of(cmd.getAllArgumentValues("--file-associations")).map( 146 Path::of).forEach(this::verifyFileAssociationsRegistry); 147 } 148 149 private void verifyFileAssociationsRegistry(Path faFile) { 150 boolean appInstalled = cmd.appLauncherPath().toFile().exists(); 151 try { 152 TKit.trace(String.format( 153 "Get file association properties from [%s] file", 154 faFile)); 155 Map<String, String> faProps = Files.readAllLines(faFile).stream().filter( 156 line -> line.trim().startsWith("extension=") || line.trim().startsWith( 157 "mime-type=")).map( 158 line -> { 159 String[] keyValue = line.trim().split("=", 2); 160 return Map.entry(keyValue[0], keyValue[1]); 161 }).collect(Collectors.toMap( 162 entry -> entry.getKey(), 163 entry -> entry.getValue())); 164 String suffix = faProps.get("extension"); 165 String contentType = faProps.get("mime-type"); 166 TKit.assertNotNull(suffix, String.format( 167 "Check file association suffix [%s] is found in [%s] property file", 168 suffix, faFile)); 169 TKit.assertNotNull(contentType, String.format( 170 "Check file association content type [%s] is found in [%s] property file", 171 contentType, faFile)); 172 verifyFileAssociations(appInstalled, "." + suffix, contentType); 173 } catch (IOException ex) { 174 throw new RuntimeException(ex); 175 } 176 } 177 178 private void verifyFileAssociations(boolean exists, String suffix, 179 String contentType) { 180 String contentTypeFromRegistry = queryRegistryValue(Path.of( 181 "HKLM\\Software\\Classes", suffix).toString(), 182 "Content Type"); 183 String suffixFromRegistry = queryRegistryValue( 184 "HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\" + contentType, 185 "Extension"); 186 187 if (exists) { 188 TKit.assertEquals(suffix, suffixFromRegistry, 189 "Check suffix in registry is as expected"); 190 TKit.assertEquals(contentType, contentTypeFromRegistry, 191 "Check content type in registry is as expected"); 192 } else { 193 TKit.assertNull(suffixFromRegistry, 194 "Check suffix in registry not found"); 195 TKit.assertNull(contentTypeFromRegistry, 196 "Check content type in registry not found"); 197 } 198 } 199 200 private final JPackageCommand cmd; 201 } 202 203 private static String queryRegistryValue(String keyPath, String valueName) { 204 Executor.Result status = new Executor() 205 .setExecutable("reg") 206 .saveOutput() 207 .addArguments("query", keyPath, "/v", valueName) 208 .execute(); 209 if (status.exitCode == 1) { 210 // Should be the case of no such registry value or key 211 String lookupString = "ERROR: The system was unable to find the specified registry key or value."; 212 status.getOutput().stream().filter(line -> line.equals(lookupString)).findFirst().orElseThrow( 213 () -> new RuntimeException(String.format( 214 "Failed to find [%s] string in the output", 215 lookupString))); 216 TKit.trace(String.format( 217 "Registry value [%s] at [%s] path not found", valueName, 218 keyPath)); 219 return null; 220 } 221 222 String value = status.assertExitCodeIsZero().getOutput().stream().skip(2).findFirst().orElseThrow(); 223 // Extract the last field from the following line: 224 // Common Desktop REG_SZ C:\Users\Public\Desktop 225 value = value.split(" REG_SZ ")[1]; 226 227 TKit.trace(String.format("Registry value [%s] at [%s] path is [%s]", 228 valueName, keyPath, value)); 229 230 return value; 231 } 232 233 private static String queryRegistryValueCache(String keyPath, 234 String valueName) { 235 String key = String.format("[%s][%s]", keyPath, valueName); 236 String value = REGISTRY_VALUES.get(key); 237 if (value == null) { 238 value = queryRegistryValue(keyPath, valueName); 239 REGISTRY_VALUES.put(key, value); 240 } 241 242 return value; 243 } 244 245 static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of( 246 "bin\\server\\jvm.dll")); 247 248 // jtreg resets %ProgramFiles% environment variable by some reason. 249 private final static Path PROGRAM_FILES = Path.of(Optional.ofNullable( 250 System.getenv("ProgramFiles")).orElse("C:\\Program Files")); 251 252 private final static Path USER_LOCAL = Path.of(System.getProperty( 253 "user.home"), 254 "AppData", "Local"); 255 256 private final static String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; 257 private final static String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; 258 259 private static final Map<String, String> REGISTRY_VALUES = new HashMap<>(); 260 }