Java 6 exec, escaping special characters within ProcessBuilder.command(String[])

numtech
numtech used Ask the Experts™
on
Hello,

I use the commandline from my main java app to execute another 'mailer.jar' app.
One of the argument is the mail content that can contains any character including \n, \r, ", '...

I thought using
ProcessBuilder.command(String[])

Open in new window

instead of  
ProcessBuilder.command(String)

Open in new window

would prevent the ProcessBuilder.exec from having troubles with splitting the command line, but it does not work.

If my command is a String[] array containing:
String[] cmd=new String[]{"java",
"-jar",
"mailer.jar"
"smtp.test.org",
"test@test.org",
"test@test.org",
"my subject ...",
"my content with carriage returns and other strange characters"};

Open in new window


I will obtain as the String[] args of the main method of my mailer.jar
String[] args=new String[]{
"smtp.test.org",
"test@test.org",
"test@test.org",
"my subject ...",
"my content with",
"carriage returns and",
"other strange characters",
...};

Open in new window


What should I do?
I would prefer not to use Apache Commons Exec because we have developped our own low level lib to handle multi threading and asynchronous exec...

Thanks for your help!

Regards,

Renaud
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Top Expert 2016

Commented:
For the final array element, does a simple string work?

Author

Commented:
Hello,

Yes it works perfectly with simple String content in the cmd.
But if one of the String in the cmd contains any " or ' character, it would be splitted in the final array.

I did this, and it solved the problem under windows, but I think it is really not a good idea to go into this because I will have to test it on every plaform/version (and I am targetting Win, MacOS and Linux).
private String[] escapeCMD(String[] commandLine) {
        String[] escapedCmd = Arrays.copyOf(commandLine, commandLine.length);
        for (int i = 0; i < escapedCmd.length; i++) {
            escapedCmd[i] = escapedCmd[i].replace("\"", "\\\"").replace("'", "\\'");
        }
        return escapedCmd;
    }

Open in new window

Author

Commented:
What a weird work around when I am not supposed to know the underlying OS specifics!
CompTIA Cloud+

The CompTIA Cloud+ Basic training course will teach you about cloud concepts and models, data storage, networking, and network infrastructure.

Top Expert 2016

Commented:
Would it not be easier just to do something like the following?
Mailer.main(args);

Open in new window

Author

Commented:
Not an option, mailer.jar cannot be loaded inside the main JVM. We have designed this to obtain a low coupling between different components that will evolve in a independant way.
Our designt is:
boot.jar
update.jar
runtime.jar
mailer.jar
The command line prototype and exit code of each main method is defined as the low level interface between application components.
We tryed the classloader approch in the past but we gave up after a while because of an memory leak issue in the unloading process (and we do not want to re-invent OSGI...)
Top Expert 2016

Commented:
I'm afraid to say that it looks like what you did in http:#35769647 IS going to have to be tested for each of your target platforms then
Mick BarryJava Developer
Top Expert 2010

Commented:
> "my content with",
> "carriage returns and",
> "other strange characters",

they are separate arguments. Are you expecting them to be treated as one?
And how are you receiving the special characters?

Author

Commented:
To illustrate I can show you the real example
This is what is sent as the commandline arguments:

java

-jar

mailer.jar

zimbra.xxxx.fr

ryyyyy@xxxx.fr

ryyyyy@xxxx.fr

NUMAILER[BOOT] RollbackSuccesfull -- satId=40436aff506a72aae8552438d493238b22d67581, path=C:/Program Files/Xxxx/Backup4, companyId=2

The install add to be rolled back.;Rollback reason was: XxxxException : The process terminated in error after 12ms, IExecutionResult[exitCode=1, stdOut="", stdErr=""] >>>> STACK TRACE :[fr.xxxx.boot.RuntimeBooter.bootRuntime(RuntimeBooter.java:125), fr.xxxx.boot.RuntimeBooter.run(RuntimeBooter.java:51), java.lang.Thread.run(Unknown Source)]

 ..\log, 25]

Open in new window


This is what I receive as the main args:
Arg[0] : zimbra.xxxx.fr

Arg[1] : ryyyyy@xxxx.fr

Arg[2] : ryyyyy@xxxx.fr

Arg[3] : NUMAILER[BOOT] RollbackSuccesfull -- satId=40436aff506a72aae8552438d493238b22d67581, path=C:/Program Files/Xxxx/Backup4, companyId=2

Arg[4] : The install add to be rolled back.;Rollback reason was: XxxxException : The process terminated in error after 17ms, IExecutionResult[exitCode=1, stdOut=",

Arg[5] : stdErr=Invalid or corrupt jarfile runtime-4.1.1.jar ;]

Arg[6] : >>>>

Arg[7] : STACK

Arg[8] : TRACE

Arg[9] : :[fr.xxxx.boot.RuntimeBooter.bootRuntime(RuntimeBooter.java:125),

Arg[10] : fr.xxxx.boot.RuntimeBooter.run(RuntimeBooter.java:51),

Arg[11] : java.lang.Thread.run(Unknown

Arg[12] : Source)] ..\log 25

Open in new window

Mick BarryJava Developer
Top Expert 2010

Commented:
Have you tested it on the other platforms?
Runtime.exec() used to be terrible at handling the command line, one would have hoped they had fixed it up for ProcessBuilder
Commented:
Ok this is the code from Apache Commons Exec to handle quoting. We can see that they escape SPACE, SINGLE_QUOTE and DOUBLE_QUOTE as explained in the method javadoc.

I think the question is closed! I whish they would create an abstract method in the ProcessBuilder "String escapeCommandArg(String)" in Java 7 that would have to be implemented natively for each platform!

Thanks guys for your help!

Regards,

Renaud


private static final String SINGLE_QUOTE = "\'";
    private static final String DOUBLE_QUOTE = "\"";
    private static final char SLASH_CHAR = '/';
    private static final char BACKSLASH_CHAR = '\\';

/**
     * Put quotes around the given String if necessary.
     * <p>
     * If the argument doesn't include spaces or quotes, return it as is. If it
     * contains double quotes, use single quotes - else surround the argument by
     * double quotes.
     * </p>
     *
     * @param argument the argument to be quoted
     * @return the quoted argument
     * @throws IllegalArgumentException If argument contains both types of quotes
     */
    public static String quoteArgument(final String argument) {

        String cleanedArgument = argument.trim();

        // strip the quotes from both ends
        while(cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) {
            cleanedArgument = cleanedArgument.substring(1);
        }
        
        while(cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) {
            cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1);
        }

        final StringBuffer buf = new StringBuffer();
        if (cleanedArgument.indexOf(DOUBLE_QUOTE) > -1) {
            if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1) {
                throw new IllegalArgumentException(
                        "Can't handle single and double quotes in same argument");
            } else {
                return buf.append(SINGLE_QUOTE).append(cleanedArgument).append(
                        SINGLE_QUOTE).toString();
            }
        } else if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1
                || cleanedArgument.indexOf(" ") > -1) {
            return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append(
                    DOUBLE_QUOTE).toString();
        } else {
            return cleanedArgument;
        }
    }

Open in new window

Mick BarryJava Developer
Top Expert 2010

Commented:
Issue is that you don't know what shell (if any) is involved.

Author

Commented:
If found myself the solution by exploring apache commons exec sources.

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial