| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Portions copyright 2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.forgerock.opendj.config.dsconfig; |
| | | |
| | | import com.forgerock.opendj.cli.ArgumentException; |
| | | import com.forgerock.opendj.cli.ReturnCode; |
| | | |
| | | import org.forgerock.testng.ForgeRockTestCase; |
| | | import org.testng.Assert; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | import java.io.BufferedReader; |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.PrintStream; |
| | | import java.io.StringReader; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | @Test(groups = { "precommit", "config" }) |
| | | public class DSConfigParseTest extends ForgeRockTestCase { |
| | |
| | | Collection<String> cmdLine = DSConfig.toCommandArgs(arg); |
| | | Assert.assertEquals(cmdLine.iterator().next(), value); |
| | | } |
| | | |
| | | /** Batch lines and the arguments a POSIX shell splits them into. */ |
| | | @DataProvider |
| | | public Object[][] shellQuoting() { |
| | | return new Object[][] { |
| | | // Inside double quotes a backslash is kept unless it precedes ", \, $ or `. |
| | | { "--set base-dn:\"cn=a\\,b,dc=x\"", args("--set", "base-dn:cn=a\\,b,dc=x") }, |
| | | { "--set \"base-dn:cn=a\\2Cb,dc=x\"", args("--set", "base-dn:cn=a\\2Cb,dc=x") }, |
| | | { "\"a\\\"b\\\\c\\$d\\`e\"", args("a\"b\\c$d`e") }, |
| | | // Single quotes keep everything literally, a backslash included. |
| | | { "--set 'base-dn:o=My Company'", args("--set", "base-dn:o=My Company") }, |
| | | { "--set 'base-dn:cn=a\\,b,dc=x'", args("--set", "base-dn:cn=a\\,b,dc=x") }, |
| | | { "'say \"hi\"' \"it's\"", args("say \"hi\"", "it's") }, |
| | | // Quoted and unquoted parts of one word make a single argument. |
| | | { "base-dn:\"o=My \"'Company'", args("base-dn:o=My Company") }, |
| | | { "\"a\"b c", args("ab", "c") }, |
| | | // An empty quoted word is an empty argument. |
| | | { "--set description:\"\" \"\"", args("--set", "description:", "") }, |
| | | // Tabs separate arguments as spaces do. |
| | | { "a\tb c", args("a", "b", "c") }, |
| | | // A trailing backslash has nothing to escape and is kept. |
| | | { "a b\\", args("a", "b\\") }, |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "shellQuoting") |
| | | public void testShellQuotingInCommandLine(String line, List<String> expected) throws Exception { |
| | | Assert.assertEquals(new ArrayList<>(DSConfig.toCommandArgs(line)), expected); |
| | | } |
| | | |
| | | /** Batch lines with a quote that is not closed: a POSIX shell rejects them. */ |
| | | @DataProvider |
| | | public Object[][] unclosedQuotes() { |
| | | return new Object[][] { |
| | | // Before single quotes were supported, this line gave three arguments. |
| | | { "--set description:O'Brien --advanced" }, |
| | | { "--set 'base-dn:o=My Company" }, |
| | | { "--set \"base-dn:o=My Company" }, |
| | | { "--set \"base-dn:o=My Company\\\"" }, |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "unclosedQuotes", expectedExceptions = ArgumentException.class, |
| | | expectedExceptionsMessageRegExp = "The following batch command has a quote that is not closed: .*") |
| | | public void testUnclosedQuoteIsRejected(String line) throws Exception { |
| | | DSConfig.toCommandArgs(line); |
| | | } |
| | | |
| | | @Test |
| | | public void testBatchCommandWithUnclosedQuoteFails() { |
| | | final ByteArrayOutputStream err = new ByteArrayOutputStream(); |
| | | |
| | | final int exitCode = DSConfig.runBatchCommand(args("--noPropertiesFile", "-n"), "set-x --set 'a", |
| | | new PrintStream(new ByteArrayOutputStream()), new PrintStream(err)); |
| | | |
| | | Assert.assertEquals(exitCode, ReturnCode.ERROR_USER_DATA.get()); |
| | | Assert.assertTrue(text(err).contains("The following batch command has a quote that is not closed: " |
| | | + "set-x --set 'a"), text(err)); |
| | | } |
| | | |
| | | @Test |
| | | public void testBatchCommandKeepsEscapedTrailingBlank() { |
| | | // dsconfig fails on the first argument, before it reads --no-prompt, so it still writes |
| | | // the error to the output stream. |
| | | final ByteArrayOutputStream output = new ByteArrayOutputStream(); |
| | | final PrintStream stream = new PrintStream(output); |
| | | |
| | | // dsconfig has no such subcommand: the error quotes the argument it was given. |
| | | final int exitCode = DSConfig.runBatchCommand(args("--noPropertiesFile", "-n"), "set-x\\ ", stream, stream); |
| | | |
| | | Assert.assertEquals(exitCode, ReturnCode.CONFLICTING_ARGS.get()); |
| | | Assert.assertTrue(text(output).contains("The provided argument \"set-x \" is not recognized"), text(output)); |
| | | } |
| | | |
| | | /** The output with every run of blanks and line breaks as a single space, as the console wraps lines. */ |
| | | private static String text(ByteArrayOutputStream out) { |
| | | return new String(out.toByteArray()).replaceAll("\\s+", " "); |
| | | } |
| | | |
| | | /** Batch files and the arguments of each command they hold. */ |
| | | @DataProvider |
| | | public Object[][] batchFiles() { |
| | | return new Object[][] { |
| | | { "# comment\n\nset-x --foo bar\nset-y\n", commands(args("set-x", "--foo", "bar"), args("set-y")) }, |
| | | // A line of blanks is skipped as an empty line is. |
| | | { "set-x\n \nset-y\n", commands(args("set-x"), args("set-y")) }, |
| | | { "set-x\n \t\n", commands(args("set-x")) }, |
| | | // Empty lines and comments are skipped inside a continued command too, so a line of a |
| | | // long command can be commented out. |
| | | { "set-x \\\n# --foo bar \\\n --baz qux\n", commands(args("set-x", "--baz", "qux")) }, |
| | | { "set-x \\\n\n --baz qux\n", commands(args("set-x", "--baz", "qux")) }, |
| | | // A line that ends in a backslash continues on the next line. |
| | | { "set-x \\\n --foo bar\n", commands(args("set-x", "--foo", "bar")) }, |
| | | { "set-x --foo ab\\\ncd\n", commands(args("set-x", "--foo", "abcd")) }, |
| | | // An escaped backslash at the end of a line does not continue it, as dsconfig |
| | | // --commandFilePath writes a value that ends in a backslash on UNIX. |
| | | { "set-x --bindPassword pa\\\\\nset-y --foo bar\n", |
| | | commands(args("set-x", "--bindPassword", "pa\\"), args("set-y", "--foo", "bar")) }, |
| | | { "set-x --foo a\\\\\\\nb\n", commands(args("set-x", "--foo", "a\\b")) }, |
| | | // An escaped blank at the end of a line is kept. |
| | | { "set-x --set description:value\\ \n", commands(args("set-x", "--set", "description:value ")) }, |
| | | // A command still runs when its last line continues at the end of the file. |
| | | { "set-x --foo bar \\", commands(args("set-x", "--foo", "bar")) }, |
| | | { "set-x\n \\\n", commands(args("set-x")) }, |
| | | }; |
| | | } |
| | | |
| | | @Test(dataProvider = "batchFiles") |
| | | public void testBatchFileCommands(String batch, List<List<String>> expected) throws Exception { |
| | | final List<List<String>> actual = new ArrayList<>(); |
| | | try (BufferedReader reader = new BufferedReader(new StringReader(batch))) { |
| | | String command; |
| | | while ((command = DSConfig.nextBatchCommand(reader)) != null) { |
| | | actual.add(new ArrayList<>(DSConfig.toCommandArgs(command))); |
| | | } |
| | | } |
| | | Assert.assertEquals(actual, expected); |
| | | } |
| | | |
| | | private static List<String> args(String... args) { |
| | | return Arrays.asList(args); |
| | | } |
| | | |
| | | @SafeVarargs |
| | | private static List<List<String>> commands(List<String>... commands) { |
| | | return Arrays.asList(commands); |
| | | } |
| | | } |