diff --git a/core/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/core/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index be78dd927ff32..cb0e82cbec6ac 100644 --- a/core/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/core/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -105,6 +105,13 @@ class InstallPluginCommand extends EnvironmentAwareCommand { private static final String PROPERTY_STAGING_ID = "es.plugins.staging"; + // exit codes for install + /** A plugin with the same name is already installed. */ + static final int PLUGIN_EXISTS = 1; + /** The plugin zip is not properly structured. */ + static final int PLUGIN_MALFORMED = 2; + + /** The builtin modules, which are plugins, but cannot be installed or removed. */ static final Set MODULES; static { @@ -333,7 +340,8 @@ private Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tm byte[] zipbytes = Files.readAllBytes(zip); String gotChecksum = MessageDigests.toHexString(MessageDigests.sha1().digest(zipbytes)); if (expectedChecksum.equals(gotChecksum) == false) { - throw new UserException(ExitCodes.IO_ERROR, "SHA1 mismatch, expected " + expectedChecksum + " but got " + gotChecksum); + throw new UserException(ExitCodes.IO_ERROR, + "SHA1 mismatch, expected " + expectedChecksum + " but got " + gotChecksum); } return zip; @@ -357,12 +365,14 @@ private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException hasEsDir = true; Path targetFile = target.resolve(entry.getName().substring("elasticsearch/".length())); - // Using the entry name as a path can result in an entry outside of the plugin dir, either if the - // name starts with the root of the filesystem, or it is a relative entry like ../whatever. - // This check attempts to identify both cases by first normalizing the path (which removes foo/..) - // and ensuring the normalized entry is still rooted with the target plugin directory. + // Using the entry name as a path can result in an entry outside of the plugin dir, + // either if the name starts with the root of the filesystem, or it is a relative + // entry like ../whatever. This check attempts to identify both cases by first + // normalizing the path (which removes foo/..) and ensuring the normalized entry + // is still rooted with the target plugin directory. if (targetFile.normalize().startsWith(target) == false) { - throw new IOException("Zip contains entry name '" + entry.getName() + "' resolving outside of plugin directory"); + throw new UserException(PLUGIN_MALFORMED, "Zip contains entry name '" + + entry.getName() + "' resolving outside of plugin directory"); } // be on the safe side: do not rely on that directories are always extracted @@ -384,7 +394,8 @@ private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException Files.delete(zip); if (hasEsDir == false) { IOUtils.rm(target); - throw new UserException(ExitCodes.DATA_ERROR, "`elasticsearch` directory is missing in the plugin zip"); + throw new UserException(PLUGIN_MALFORMED, + "`elasticsearch` directory is missing in the plugin zip"); } return target; } @@ -424,10 +435,11 @@ private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch, E if (Files.exists(destination)) { final String message = String.format( Locale.ROOT, - "plugin directory [%s] already exists; if you need to update the plugin, uninstall it first using command 'remove %s'", + "plugin directory [%s] already exists; if you need to update the plugin, " + + "uninstall it first using command 'remove %s'", destination.toAbsolutePath(), info.getName()); - throw new UserException(ExitCodes.CONFIG, message); + throw new UserException(PLUGIN_EXISTS, message); } terminal.println(VERBOSE, info.toString()); @@ -435,8 +447,8 @@ private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch, E // don't let user install plugin as a module... // they might be unavoidably in maven central and are packaged up the same way) if (MODULES.contains(info.getName())) { - throw new UserException( - ExitCodes.USAGE, "plugin '" + info.getName() + "' cannot be installed like this, it is a system module"); + throw new UserException(ExitCodes.USAGE, "plugin '" + info.getName() + + "' cannot be installed like this, it is a system module"); } // check for jar hell before any copying @@ -533,7 +545,7 @@ public FileVisitResult visitFile(Path pluginFile, BasicFileAttributes attrs) thr /** Copies the files from {@code tmpBinDir} into {@code destBinDir}, along with permissions from dest dirs parent. */ private void installBin(PluginInfo info, Path tmpBinDir, Path destBinDir) throws Exception { if (Files.isDirectory(tmpBinDir) == false) { - throw new UserException(ExitCodes.IO_ERROR, "bin in plugin " + info.getName() + " is not a directory"); + throw new UserException(PLUGIN_MALFORMED, "bin in plugin " + info.getName() + " is not a directory"); } Files.createDirectory(destBinDir); setFileAttributes(destBinDir, BIN_DIR_PERMS); @@ -541,9 +553,8 @@ private void installBin(PluginInfo info, Path tmpBinDir, Path destBinDir) throws try (DirectoryStream stream = Files.newDirectoryStream(tmpBinDir)) { for (Path srcFile : stream) { if (Files.isDirectory(srcFile)) { - throw new UserException( - ExitCodes.DATA_ERROR, - "Directories not allowed in bin dir for plugin " + info.getName() + ", found " + srcFile.getFileName()); + throw new UserException(PLUGIN_MALFORMED, "Directories not allowed in bin dir " + + "for plugin " + info.getName() + ", found " + srcFile.getFileName()); } Path destFile = destBinDir.resolve(tmpBinDir.relativize(srcFile)); @@ -560,7 +571,8 @@ private void installBin(PluginInfo info, Path tmpBinDir, Path destBinDir) throws */ private void installConfig(PluginInfo info, Path tmpConfigDir, Path destConfigDir) throws Exception { if (Files.isDirectory(tmpConfigDir) == false) { - throw new UserException(ExitCodes.IO_ERROR, "config in plugin " + info.getName() + " is not a directory"); + throw new UserException(PLUGIN_MALFORMED, + "config in plugin " + info.getName() + " is not a directory"); } Files.createDirectories(destConfigDir); @@ -576,7 +588,8 @@ private void installConfig(PluginInfo info, Path tmpConfigDir, Path destConfigDi try (DirectoryStream stream = Files.newDirectoryStream(tmpConfigDir)) { for (Path srcFile : stream) { if (Files.isDirectory(srcFile)) { - throw new UserException(ExitCodes.DATA_ERROR, "Directories not allowed in config dir for plugin " + info.getName()); + throw new UserException(PLUGIN_MALFORMED, + "Directories not allowed in config dir for plugin " + info.getName()); } Path destFile = destConfigDir.resolve(tmpConfigDir.relativize(srcFile)); diff --git a/qa/evil-tests/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/qa/evil-tests/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 8d99ab8e89d42..732852ca1533b 100644 --- a/qa/evil-tests/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/qa/evil-tests/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -595,7 +595,7 @@ public void testZipRelativeOutsideEntryName() throws Exception { stream.putNextEntry(new ZipEntry("elasticsearch/../blah")); } String pluginZip = zip.toUri().toURL().toString(); - IOException e = expectThrows(IOException.class, () -> installPlugin(pluginZip, env.v1())); + UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1())); assertTrue(e.getMessage(), e.getMessage().contains("resolving outside of plugin directory")); }