mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
yesterday e9e614a4644288148abe6cb6fae3112e25a86d2d
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
@@ -1394,40 +1394,38 @@
        try (BufferedReader bReader = batchCommandsReader()) {
            List<String> initialArgs = removeBatchArgs(args);
            // Split the CLI string into arguments array
            String command = "";
            String line;
            while ((line = bReader.readLine()) != null) {
                if (line.isEmpty() || line.startsWith("#")) {
                    // Empty line or comment
                    continue;
                }
                // command split in several line support
                if (line.endsWith("\\")) {
                    // command is split into several lines
                    command += line.substring(0, line.length() - 1);
                    continue;
                }
            String command;
            while ((command = nextBatchCommand(bReader)) != null) {
                // Only the echo is trimmed: an escaped blank at the end of the command is kept.
                printlnNoWrap(LocalizableMessage.raw(command.trim()));
                command += line;
                command = command.trim();
                printlnNoWrap(LocalizableMessage.raw(command));
                // Append initial arguments to the file line
                final String[] allArgsArray = buildCommandArgs(initialArgs, command);
                int exitCode = main(allArgsArray, getOutputStream(), getErrorStream());
                final int exitCode = runBatchCommand(initialArgs, command, getOutputStream(), getErrorStream());
                if (exitCode != ReturnCode.SUCCESS.get()) {
                    System.exit(filterExitCode(exitCode));
                }
                println();
                // reset command
                command = "";
            }
        } catch (IOException ex) {
            errPrintln(ERR_DSCFG_ERROR_READING_BATCH_FILE.get(ex));
        }
    }
    /**
     * Runs one command of a batch with the initial arguments appended, and returns its exit code.
     * A command that cannot be split into arguments is reported on the error stream: a batch
     * always runs with --no-prompt, so that is where dsconfig writes its errors.
     */
    static int runBatchCommand(final List<String> initialArgs, final String command, final PrintStream out,
            final PrintStream err) {
        try {
            // Append initial arguments to the file line
            return main(buildCommandArgs(initialArgs, command), out, err);
        } catch (final ArgumentException e) {
            err.println(wrapText(e.getMessageObject(), MAX_LINE_WIDTH));
            return ReturnCode.ERROR_USER_DATA.get();
        }
    }
    private BufferedReader batchCommandsReader() throws FileNotFoundException {
        if (batchArgument.isPresent()) {
            return new BufferedReader(new InputStreamReader(System.in));
@@ -1440,7 +1438,38 @@
        }
    }
    private String[] buildCommandArgs(List<String> initialArgs, String batchCommand) {
    /**
     * Reads the next command of a batch, or returns {@code null} at its end. Lines that are empty
     * or hold only blanks, and comments, are skipped, also inside a command that continues over
     * several lines, and a line that ends in an unescaped backslash continues on the next line. A
     * command whose last line continues still runs at the end of the batch.
     */
    static String nextBatchCommand(final BufferedReader reader) throws IOException {
        final StringBuilder command = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.trim().isEmpty() || line.startsWith("#")) {
                // Empty or blank line, or comment
                continue;
            }
            if (!continuesOnNextLine(line)) {
                return command.append(line).toString();
            }
            command.append(line, 0, line.length() - 1);
        }
        return command.toString().trim().isEmpty() ? null : command.toString();
    }
    /** Whether a line ends in a backslash that is not itself escaped by a backslash. */
    private static boolean continuesOnNextLine(final String line) {
        int backslashes = 0;
        for (int i = line.length() - 1; i >= 0 && line.charAt(i) == '\\'; i--) {
            backslashes++;
        }
        return backslashes % 2 == 1;
    }
    private static String[] buildCommandArgs(List<String> initialArgs, String batchCommand) throws ArgumentException {
        final Collection<String> commandArgs = toCommandArgs(batchCommand);
        final int length = commandArgs.size() + initialArgs.size();
        final List<String> allArguments = new ArrayList<>(length);
@@ -1449,48 +1478,66 @@
        return allArguments.toArray(new String[length]);
    }
    static Collection<String> toCommandArgs(String command) {
        Collection<String> commandArgs = new ArrayList<>();
        StringBuilder builder = new StringBuilder();
        boolean inQuotes = false;
    /**
     * Splits a batch line into arguments the way a POSIX shell does, without any expansion: a
     * backslash outside quotes escapes the next char, single quotes keep everything literally, and
     * inside double quotes a backslash escapes only {@code "}, {@code \}, {@code $} and {@code `}.
     *
     * @throws ArgumentException
     *             If a quote is not closed.
     */
    static Collection<String> toCommandArgs(String command) throws ArgumentException {
        final Collection<String> commandArgs = new ArrayList<>();
        final StringBuilder builder = new StringBuilder();
        // A word that holds only quotes is still an (empty) argument.
        boolean inWord = false;
        for (int i = 0; i < command.length(); i++) {
            final char c = command.charAt(i);
            switch (c) {
            case ' ':
            case '\t':
                if (inWord) {
                    commandArgs.add(builder.toString());
                    builder.setLength(0);
                    inWord = false;
                }
                continue;
            case '\\':
                // A trailing backslash has nothing to escape and is kept.
                builder.append(i + 1 < command.length() ? command.charAt(++i) : c);
                break;
            case '\'':
                final int end = command.indexOf('\'', i + 1);
                if (end < 0) {
                    throw new ArgumentException(ERR_DSCFG_ERROR_BATCH_UNCLOSED_QUOTE.get(command.trim()));
                }
                builder.append(command, i + 1, end);
                i = end;
                break;
            case '"':
                for (i++; i < command.length() && command.charAt(i) != '"'; i++) {
                    if (command.charAt(i) == '\\' && i + 1 < command.length()
                            && "\"\\$`".indexOf(command.charAt(i + 1)) >= 0) {
                        i++;
                    }
                    builder.append(command.charAt(i));
                }
                if (i == command.length()) {
                    throw new ArgumentException(ERR_DSCFG_ERROR_BATCH_UNCLOSED_QUOTE.get(command.trim()));
                }
                break;
            default:
                builder.append(c);
                break;
            case '\\':
                builder.append(command.charAt(++i));
                break;
            case '"':
                if (inQuotes) {
                    builder = newArgumentString(commandArgs, builder);
                    inQuotes = false;
                } else {
                    inQuotes = true;
                }
                break;
            case ' ':
                if (inQuotes) {
                    builder.append(c);
                } else {
                    builder = newArgumentString(commandArgs, builder);
                }
                break;
            }
            inWord = true;
        }
        newArgumentString(commandArgs, builder);
        if (inWord) {
            commandArgs.add(builder.toString());
        }
        return commandArgs;
    }
    private static StringBuilder newArgumentString(Collection<String> commandArgs, StringBuilder stringBuilder) {
        if (stringBuilder.length() > 0) {
            commandArgs.add(stringBuilder.toString());
            stringBuilder = new StringBuilder();
        }
        return stringBuilder;
    }
    private List<String> removeBatchArgs(String[] args) {
        // Build a list of initial arguments,
        // removing the batch file option + its value