1 /* 2 * Copyright (c) 2014, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jpackage.internal; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.PrintStream; 32 import java.nio.file.Files; 33 import java.text.MessageFormat; 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.Collection; 37 import java.util.LinkedHashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.ResourceBundle; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 import static jdk.jpackage.internal.StandardBundlerParam.*; 45 46 public abstract class MacBaseInstallerBundler extends AbstractBundler { 47 48 private static final ResourceBundle I18N = ResourceBundle.getBundle( 49 "jdk.jpackage.internal.resources.MacResources"); 50 51 // This could be generalized more to be for any type of Image Bundler 52 public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER = 53 new StandardBundlerParam<>( 54 I18N.getString("param.app-bundler.name"), 55 I18N.getString("param.app-bundle.description"), 56 "mac.app.bundler", 57 MacAppBundler.class, 58 params -> new MacAppBundler(), 59 (s, p) -> null); 60 61 public final BundlerParamInfo<File> APP_IMAGE_BUILD_ROOT = 62 new StandardBundlerParam<>( 63 I18N.getString("param.app-image-build-root.name"), 64 I18N.getString("param.app-image-build-root.description"), 65 "mac.app.imageRoot", 66 File.class, 67 params -> { 68 File imageDir = IMAGES_ROOT.fetchFrom(params); 69 if (!imageDir.exists()) imageDir.mkdirs(); 70 try { 71 return Files.createTempDirectory( 72 imageDir.toPath(), "image-").toFile(); 73 } catch (IOException e) { 74 return new File(imageDir, getID()+ ".image"); 75 } 76 }, 77 (s, p) -> new File(s)); 78 79 public static final BundlerParamInfo<String> SIGNING_KEY_USER = 80 new StandardBundlerParam<>( 81 I18N.getString("param.signing-key-name.name"), 82 I18N.getString("param.signing-key-name.description"), 83 Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId(), 84 String.class, 85 params -> "", 86 null); 87 88 public static final BundlerParamInfo<String> SIGNING_KEYCHAIN = 89 new StandardBundlerParam<>( 90 I18N.getString("param.signing-keychain.name"), 91 I18N.getString("param.signing-keychain.description"), 92 Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), 93 String.class, 94 params -> "", 95 null); 96 97 public static final BundlerParamInfo<String> INSTALLER_NAME = 98 new StandardBundlerParam<> ( 99 I18N.getString("param.installer-name.name"), 100 I18N.getString("param.installer-name.description"), 101 "mac.installerName", 102 String.class, 103 params -> { 104 String nm = APP_NAME.fetchFrom(params); 105 if (nm == null) return null; 106 107 String version = VERSION.fetchFrom(params); 108 if (version == null) { 109 return nm; 110 } else { 111 return nm + "-" + version; 112 } 113 }, 114 (s, p) -> s); 115 116 protected void validateAppImageAndBundeler( 117 Map<String, ? super Object> params) 118 throws ConfigException, UnsupportedPlatformException { 119 if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) { 120 File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params); 121 if (!applicationImage.exists()) { 122 throw new ConfigException( 123 MessageFormat.format(I18N.getString( 124 "message.app-image-dir-does-not-exist"), 125 PREDEFINED_APP_IMAGE.getID(), 126 applicationImage.toString()), 127 MessageFormat.format(I18N.getString( 128 "message.app-image-dir-does-not-exist.advice"), 129 PREDEFINED_APP_IMAGE.getID())); 130 } 131 if (APP_NAME.fetchFrom(params) == null) { 132 throw new ConfigException( 133 I18N.getString("message.app-image-requires-app-name"), 134 I18N.getString( 135 "message.app-image-requires-app-name.advice")); 136 } 137 if (IDENTIFIER.fetchFrom(params) == null) { 138 throw new ConfigException( 139 I18N.getString("message.app-image-requires-identifier"), 140 I18N.getString( 141 "message.app-image-requires-identifier.advice")); 142 } 143 } else { 144 APP_BUNDLER.fetchFrom(params).validate(params); 145 } 146 } 147 148 protected File prepareAppBundle( 149 Map<String, ? super Object> p, boolean pkg) { 150 File predefinedImage = StandardBundlerParam.getPredefinedAppImage(p); 151 if (predefinedImage != null) { 152 return predefinedImage; 153 } 154 File appImageRoot = APP_IMAGE_BUILD_ROOT.fetchFrom(p); 155 if (pkg) { 156 // create pkg in dmg 157 return new MacPkgBundler().bundle(p, appImageRoot); 158 } else { 159 return APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); 160 } 161 } 162 163 @Override 164 public Collection<BundlerParamInfo<?>> getBundleParameters() { 165 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 166 167 results.addAll(MacAppBundler.getAppBundleParameters()); 168 results.addAll(Arrays.asList( 169 APP_BUNDLER, 170 CONFIG_ROOT, 171 APP_IMAGE_BUILD_ROOT, 172 PREDEFINED_APP_IMAGE 173 )); 174 175 return results; 176 } 177 178 @Override 179 public String getBundleType() { 180 return "INSTALLER"; 181 } 182 183 public static String findKey(String key, String keychainName, 184 boolean verbose) { 185 if (Platform.getPlatform() != Platform.MAC) { 186 return null; 187 } 188 189 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 190 PrintStream ps = new PrintStream(baos)) { 191 List<String> searchOptions = new ArrayList<>(); 192 searchOptions.add("security"); 193 searchOptions.add("find-certificate"); 194 searchOptions.add("-c"); 195 searchOptions.add(key); 196 searchOptions.add("-a"); 197 if (keychainName != null && !keychainName.isEmpty()) { 198 searchOptions.add(keychainName); 199 } 200 201 ProcessBuilder pb = new ProcessBuilder(searchOptions); 202 203 IOUtils.exec(pb, verbose, false, ps); 204 Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\""); 205 Matcher m = p.matcher(baos.toString()); 206 if (!m.find()) { 207 Log.error("Did not find a key matching '" + key + "'"); 208 return null; 209 } 210 String matchedKey = m.group(1); 211 if (m.find()) { 212 Log.error("Found more than one key matching '" + key + "'"); 213 return null; 214 } 215 Log.debug("Using key '" + matchedKey + "'"); 216 return matchedKey; 217 } catch (IOException ioe) { 218 Log.verbose(ioe); 219 return null; 220 } 221 } 222 }