Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do not automatically quote system properties containing spaces #9999

Closed
wants to merge 2 commits into from

Conversation

lachlan-roberts
Copy link
Contributor

See Issue jetty/jetty.docker#148

Currently this command

 java -jar ~/webtide/jetty-10.0.x/jetty-home/target/jetty-home/start.jar -Dprop="\"foo bar\"" --dry-run

Quotes the system property like this in the generated command '-Dprop="foo bar"'.
This means it is impossible to use spaces in properties.

Because of this I have removed the use of CommandLineBuildertoQuotedString() and use CommandLineBuilder.toString() instead. And if you want the args quoted it will have to be done explicitly.

@lachlan-roberts lachlan-roberts requested a review from joakime June 30, 2023 05:06
Copy link
Contributor

@gregw gregw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit concerned that since we added the quoting of start args, we are finding issues bit by bit. Is reverting this going to cause new issues or return to an existing issue.

@joakime can you confirm that this change still passes the tests you created for why the quoting was changed in the first place?

@janbartel
Copy link
Contributor

@joakime nudge?

joakime
joakime previously requested changes Jul 5, 2023
Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This undoes the fix for Issue #9309

@@ -441,7 +441,7 @@ public void start(StartArgs args) throws IOException, InterruptedException
{
CommandLineBuilder cmd = args.getMainArgs(args.getDryRunParts());
cmd.debug();
System.out.println(cmd.toQuotedString());
System.out.println(cmd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks jetty.sh usage.
See #9309

I'll try to write a DistributionTest (in this branch) that uses jetty.sh (test would be limited to only executing on Linux env)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is tricky, as it's a forked Exec (to bash + jetty.sh) that execs a JVM.
Getting the pid to stop the running JVM isn't that straight forward. (nor does jetty.sh stop work on this forked/execed JVM)

I'm not going to continue with this testcase, I'm abandoning this effort.

Here's the scenario, in the form of a jetty base, that would cause the issue in #9309

[requestlog-custom-base]$ tree -F
./
├── logs/
├── resources/
│   └── jetty-logging.properties
├── start.d/
│   ├── custom-system-props.ini
│   ├── deploy.ini
│   ├── http.ini
│   └── requestlog.ini
└── webapps/

4 directories, 5 files
[requestlog-custom-base]$ cat start.d/deploy.ini 
--module=deploy
[requestlog-custom-base]$ cat start.d/http.ini 
--module=http
[requestlog-custom-base]$ cat start.d/requestlog.ini 
--module=requestlog
jetty.requestlog.formatString=%{client}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"

[requestlog-custom-base]$ cat start.d/custom-system-props.ini 
--exec
-Dprop="foo bar"
[requestlog-custom-base]$

@joakime joakime dismissed their stale review July 5, 2023 14:26

Command line testing with jetty.sh shows this PR seems to be working

@joakime
Copy link
Contributor

joakime commented Jul 5, 2023

Testing this branch jetty-10.0.x-quotedStartArgs with a HEAD of ...

[jetty-10.0.x-quotedStartArgs]$ git log -1
commit 0cdbb583f59a9a001406f85d2d4cb74026db4e43 (HEAD -> jetty-10.0.x-quotedStartArgs, origin/jetty-10.0.x-quotedStartArgs)
Merge: 84ee447e66 17c593f9ea
Author: Lachlan Roberts <[email protected]>
Date:   Mon Jul 3 09:26:56 2023 +1000

    Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-quotedStartArgs

Using the previously mentioned requestlog-custom-base (from comment #9999 (comment))

The output is as follows ...

requestlog-custom-base]$ java -jar ~/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/start.jar --dry-run
/home/joakim/java/jvm/jdk-17.0.6+10/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/joakim/code/jetty/jetty.project
-10.0.x/jetty-home/target/jetty-home -Djetty.base=/home/joakim/code/jetty/distros/bases/requestlog-custom-base -Dprop
="foo bar" --class-path /home/joakim/code/jetty/distros/bases/requestlog-custom-base/resources:/home/joakim/code/jett
y/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/logging/slf4j-api-2.0.5.jar:/home/joakim/code/jetty/jetty.pro
ject-10.0.x/jetty-home/target/jetty-home/lib/logging/jetty-slf4j-impl-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/je
tty.project-10.0.x/jetty-home/target/jetty-home/lib/jetty-servlet-api-4.0.6.jar:/home/joakim/code/jetty/jetty.project
-10.0.x/jetty-home/target/jetty-home/lib/jetty-http-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x
/jetty-home/target/jetty-home/lib/jetty-server-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jett
y-home/target/jetty-home/lib/jetty-xml-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/t
arget/jetty-home/lib/jetty-util-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/j
etty-home/lib/jetty-io-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home
/lib/jetty-security-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/li
b/jetty-servlet-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/je
tty-webapp-10.0.16-SNAPSHOT.jar:/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/jetty-d
eploy-10.0.16-SNAPSHOT.jar org.eclipse.jetty.xml.XmlConfiguration 'java.version'='17.0.6' 'java.version.major'='17' '
java.version.micro'='6' 'java.version.minor'='0' 'java.version.platform'='17' 'jetty.base'='/home/joakim/code/jetty/d
istros/bases/requestlog-custom-base' 'jetty.base.uri'='file:///home/joakim/code/jetty/distros/bases/requestlog-custom
-base' 'jetty.home'='/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home' 'jetty.home.uri'='fil
e:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home' 'jetty.requestlog.formatString'='%{cli
ent}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"' 'jetty.webapp.addServerClasses
'='org.eclipse.jetty.logging.,file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/lo
gging/,org.slf4j.' 'prop'='"foo bar"' 'runtime.feature.alpn'='true' 'slf4j.version'='2.0.5' /home/joakim/code/jetty/j
etty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-bytebufferpool.xml /home/joakim/code/jetty/jetty.project-1
0.0.x/jetty-home/target/jetty-home/etc/jetty-threadpool.xml /home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/t
arget/jetty-home/etc/jetty.xml /home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-we
bapp.xml /home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-deploy.xml /home/joakim/
code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-http.xml /home/joakim/code/jetty/jetty.project
-10.0.x/jetty-home/target/jetty-home/etc/jetty-requestlog.xml

Which shows that the 2 relevant properties to this PR as ...

  1. a JVM system property
    -Dprop="foo bar"
    
  2. the Jetty properties (above system property and custom requestlog property)
    'prop'='"foo bar"'
    'jetty.requestlog.formatString'='%{client}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"'
    

@joakime
Copy link
Contributor

joakime commented Jul 5, 2023

Digging into this some more, as I was curious how this would impact the behavior of normal vs forked runs too.

So, I used the args-dump tool from https://github.com/joakime/args-dump/releases/tag/v1.1

[requestlog-custom-base]$ cat start.d/args-dump.ini 
--lib=/home/joakim/java/args-dump-1.1.jar
main.class=net.erdfelt.ArgsDump

With system property and --exec

[requestlog-custom-base]$ ls -la start.d/
total 28
-rw-rw-r-- 1 joakim joakim   74 Jul  5 10:07 args-dump.ini
-rw-rw-r-- 1 joakim joakim   24 Jul  5 09:14 custom-system-props.ini
-rw-rw-r-- 1 joakim joakim   16 Jul  5 09:19 deploy.ini
-rw-rw-r-- 1 joakim joakim   14 Jul  5 09:19 http.ini
-rw-rw-r-- 1 joakim joakim  142 Jul  5 09:10 requestlog.ini

[requestlog-custom-base]$ java -jar ~/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/start.jar 
WARN  : Forking second JVM due to forking module(s): []. Use --dry-run to generate the command line to avoid forking.
System Property[jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
System Property[jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
System Property[prop]: ["foo bar"]
args.length=8
args[0]: [/tmp/start_16907308263693125229.properties]
  > [java.version]: [17.0.6]
  > [java.version.major]: [17]
  > [java.version.micro]: [6]
  > [java.version.minor]: [0]
  > [java.version.platform]: [17]
  > [jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.base.uri]: [file:///home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.home.uri]: [file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.requestlog.formatString]: [%{client}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"]
  > [jetty.webapp.addServerClasses]: [org.eclipse.jetty.logging.,file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/logging/,org.slf4j.]
  > [main.class]: [net.erdfelt.ArgsDump]
  > [prop]: ["foo bar"]
  > [runtime.feature.alpn]: [true]
  > [slf4j.version]: [2.0.5]
args[1]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-bytebufferpool.xml]
args[2]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-threadpool.xml]
args[3]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty.xml]
args[4]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-webapp.xml]
args[5]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-deploy.xml]
args[6]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-http.xml]
args[7]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-requestlog.xml]

Without system property (and no forking) ...

[requestlog-custom-base]$ ls -la start.d/
total 28
-rw-rw-r-- 1 joakim joakim   74 Jul  5 10:07 args-dump.ini
-rw-rw-r-- 1 joakim joakim   24 Jul  5 09:14 custom-system-props.ini.disabled
-rw-rw-r-- 1 joakim joakim   16 Jul  5 09:19 deploy.ini
-rw-rw-r-- 1 joakim joakim   14 Jul  5 09:19 http.ini
-rw-rw-r-- 1 joakim joakim  142 Jul  5 09:10 requestlog.ini

[requestlog-custom-base]$ java -jar ~/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/start.jar 
System Property[jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
System Property[jetty.build]: [0cdbb583f59a9a001406f85d2d4cb74026db4e43]
System Property[jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
System Property[jetty.tag.version]: [jetty-10.0.16-SNAPSHOT]
System Property[jetty.version]: [10.0.16-SNAPSHOT]
args.length=8
args[0]: [/tmp/start_13265625395446227432.properties]
  > [java.version]: [17.0.6]
  > [java.version.major]: [17]
  > [java.version.micro]: [6]
  > [java.version.minor]: [0]
  > [java.version.platform]: [17]
  > [jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.base.uri]: [file:///home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.home.uri]: [file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.requestlog.formatString]: [%{client}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"]
  > [jetty.webapp.addServerClasses]: [org.eclipse.jetty.logging.,file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/logging/,org.slf4j.]
  > [main.class]: [net.erdfelt.ArgsDump]
  > [runtime.feature.alpn]: [true]
  > [slf4j.version]: [2.0.5]
args[1]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-bytebufferpool.xml]
args[2]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-threadpool.xml]
args[3]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty.xml]
args[4]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-webapp.xml]
args[5]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-deploy.xml]
args[6]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-http.xml]
args[7]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-requestlog.xml]

Aside: I find it interesting that we are not consistent with our System Properties between normal vs forked JVMs (but that's a new issue, not related this issue, opened as #10065).

The System Property shows up as key prop and value "foo bar".
But if you set that same system property via command line like so ...

$ java -Dprop="foo bar" -jar ~/java/args-dump-1.1.jar xxx yyy="yup" zzz
System Property[prop]: [foo bar]
args.length=3
args[0]: [xxx]
args[1]: [yyy=yup]
args[2]: [zzz]

That means our own internal handling of a system property from a start.d/*.ini isn't correct either.

@joakime
Copy link
Contributor

joakime commented Jul 5, 2023

If we use a non-quoted system property in a start.d/*.ini and then attempt to use it via --dry-run we get a bad command line.

Example:

[requestlog-custom-base]$ cat start.d/custom-system-props.ini 
--exec
-Dprop=foo bar

[requestlog-custom-base]$ java -jar ~/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/start.jar --dry-run
...

The output shows ... .../bin/java -Djetty.home=/opt/jetty-home -Djetty.base=/opt/requestlog-custom-base -Dprop=foo bar --class-path ...
Which is an invalid command line system property.

BUT, if we use start.jar directly we see ...

[requestlog-custom-base]$ java -jar ~/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/start.jar
WARN  : Forking second JVM due to forking module(s): []. Use --dry-run to generate the command line to avoid forking.
System Property[jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
System Property[jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
System Property[prop]: [foo bar]
args.length=8
args[0]: [/tmp/start_8821703352711841981.properties]
  > [java.version]: [17.0.6]
  > [java.version.major]: [17]
  > [java.version.micro]: [6]
  > [java.version.minor]: [0]
  > [java.version.platform]: [17]
  > [jetty.base]: [/home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.base.uri]: [file:///home/joakim/code/jetty/distros/bases/requestlog-custom-base]
  > [jetty.home]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.home.uri]: [file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home]
  > [jetty.requestlog.formatString]: [%{client}a - %u %{dd/MMM/yyyy:HH:mm:ss ZZZ|GMT}t "%r" %s %O "%{Referer}i" "%{User-Agent}i"]
  > [jetty.webapp.addServerClasses]: [org.eclipse.jetty.logging.,file:///home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/lib/logging/,org.slf4j.]
  > [main.class]: [net.erdfelt.ArgsDump]
  > [prop]: [foo bar]
  > [runtime.feature.alpn]: [true]
  > [slf4j.version]: [2.0.5]
args[1]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-bytebufferpool.xml]
args[2]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-threadpool.xml]
args[3]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty.xml]
args[4]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-webapp.xml]
args[5]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-deploy.xml]
args[6]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-http.xml]
args[7]: [/home/joakim/code/jetty/jetty.project-10.0.x/jetty-home/target/jetty-home/etc/jetty-requestlog.xml]

Which is now a correctly formatted system property for prop key. (no quotes on the value)

Copy link
Contributor

@joakime joakime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change has broken various use cases.

@@ -441,7 +441,7 @@ public void start(StartArgs args) throws IOException, InterruptedException
{
CommandLineBuilder cmd = args.getMainArgs(args.getDryRunParts());
cmd.debug();
System.out.println(cmd.toQuotedString());
System.out.println(cmd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be fixed, but I suspect in a grander scale than this PR currently has done.

The quoting is here to compensate for (system property) arguments specified in start.d/*.ini files.
Reminder, the quoting -Dprop="foo bar" is not what is used in a start.d/*.ini file, as that double quote only exists for shell usages.
As each line in an INI is a whole command line argument, the normal start.jar and even forked (usage of --exec) will operate properly (thanks be to the List<String> separation of arguments for either direct invocation of the main class, or the forked JVM use of java.lang.ProcessBuilder(List<String>))

Removing it breaks that kind of usage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joakime Sorry but I'm not follow the pronouns... what "it" needs to be removed? what "this" needs to be fixed?
Can you and @lachlan-roberts get together and come up with a grander scale fix for this. Good point that our ini files are already expanded, so that any quotes in them are literal quotes not shell quotes. We need to be very clear about all our spaces if they are expanded or not... and the bottom line is that the output of --dry-run needs to be able to be executed by a shell or by a shell script.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bigger than --dry-run alone ...

Use Cases (A): --dry-run output use/consumption

  1. Executed by unix shell directly (eg: exec $ARGS)
  2. Streamed into executable / pipe in unix via args (eg: xargs)
  3. Used as arguments in java daemon
  4. Used as arguments in linux systemd startup metadata
  5. Used as arguments in windows daemon
  6. Used in jetty.sh

Use Cases (B): Quoting of arguments by start.jar

  1. From command line
  2. From start.d/*.ini with quotes
  3. From start.d/*.ini without quotes
  4. From module declaration with quotes
  5. From module declarattion without quotes

Use Cases (C): How the arguments are consumed

  1. The output of --dry-run
  2. Direct main.class invoke of main(String[] args)
  3. Use in java.lang.ProcessBuilder(List<String> args)

The change that lachlan did in this PR ...

  • Addressed use cases A1, A2, A6, B1, B2, B4, C1
  • But Broke use cases A3, A4, A5, B3+C2, B3+C3, B5+C2, B5+C3

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joakime @lachlan-roberts

This is bigger than --dry-run alone ...

Agreed. Rather than arbitrarily going through start.jar adding or removing quotes, we need to step back and be very rigorous in understanding what spaces we have and if they are quoted or unquoted.

Use Cases (A): --dry-run output use/consumption

  1. Executed by unix shell directly (eg: exec $ARGS)

Obviously this is a quoted valued, so that it can be cut and pasted on the command line and executed.

  1. Streamed into executable / pipe in unix via args (eg: xargs)

ditto

  1. Used as arguments in java daemon

ditto

  1. Used as arguments in linux systemd startup metadata

ditto

  1. Used as arguments in windows daemon

Interesting... are windows command line quoting conventions the same as unix?

  1. Used in jetty.sh

This is also a quoted value.

I.e. The output of --dry-run should be quoted as necessary in all circumstances so that it can be correctly interpreted by a shell.

Use Cases (B): Quoting of arguments by start.jar

I think this is the more interesting area and probably where our problems lie:

  1. From command line

The command line is expanded by the shell, thus the arguments that we receive are unquoted.

  1. From start.d/*.ini with quotes

Our ini files are line separated, so they do not need to be quoted just for separation and preservation of spaces. So is this an unquoted space?

However, we do expand ${name} variables in our ini-files, so do we need an escape if an argument needs to have a value that actually contains ${? If we need an escape, then this perhaps a quoted space? Or some hybrid where we assume every line is preceded and terminated with a "?

  1. From start.d/*.ini without quotes

As above.

  1. From module declaration with quotes

Lines from mod files are copied directly to be lines in ini files, so whatever space ini files are in, then mod file lines are the same.

  1. From module declarattion without quotes

As above.

So I do not think our issue is with the output of --dry-run, as that simply must be correctly quoted so that a shell understands the argument separators and values.
I think our issue is with our inputs and determining if they are or are not quoted.

Specifically for this particular issue:

  • we have an input of -Dprop="\"foo bar\"" given on the command line. this is a quoted value
  • this is after the -jar argument so it is passed to the main method as an argument with value prop="foo bar", which is unquoted
  • The output of the --dry-run requotes this (as it should) to be '-Dprop="foo bar"'. I'm not seeing the problem with this? Perhaps it would be better to quote name and value separately to -Dprop="foo bar"'`, but the value should be the same..... ok so I'm not understanding part of the original problem..... I'll experiment some more before commenting more.

@gregw
Copy link
Contributor

gregw commented Jul 7, 2023

@joakime @lachlan-roberts I've done a bit of experimentation and I can definitely see that start.jar is quoting things: incorrectly; too often; and sometimes not when needed. In an empty directory I ran the command:

> java -jar ../start.jar -Dprop1="foo bar" -Dprop2="foo'bar" --dry-run

Which produced the output:

/usr/lib/jvm/java-17-openjdk-amd64/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home -Djetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base -Dprop2=foo'bar '-Dprop1=foo bar' --class-path  org.eclipse.jetty.xml.XmlConfiguration 'java.version'='17.0.7' 'java.version.major'='17' 'java.version.micro'='7' 'java.version.minor'='0' 'java.version.platform'='17' 'jetty.base'='/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base' 'jetty.base.uri'='file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base' 'jetty.home'='/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home' 'jetty.home.uri'='file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home' ''prop1'='foo bar'' 'prop2'='foo'bar' 'runtime.feature.alpn'='true'

which I'll line break here for clarity (it would be good to have a --dry-run=multiline variant that did this) :

/usr/lib/jvm/java-17-openjdk-amd64/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
-Djetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
-Dprop2=foo'bar \
'-Dprop1=foo bar' \
--class-path  org.eclipse.jetty.xml.XmlConfiguration \
'java.version'='17.0.7' \
'java.version.major'='17' \
'java.version.micro'='7' \
'java.version.minor'='0' \
'java.version.platform'='17' \
'jetty.base'='/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base' \
'jetty.base.uri'='file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base' \
'jetty.home'='/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home' \
'jetty.home.uri'='file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home' \
''prop1'='foo bar'' \
'prop2'='foo'bar' \
'runtime.feature.alpn'='true'

The problems I can see with this are as follows:

  • prop2 as a system property is not quoted when it should be
  • prop1 as a system property has the whole arg quoted. It is more readable to quote just the name and/or value: -Dprop1='foo bar'
  • We are quoting when we have no need to quote
  • We are passing the java.version as properties when we don't need to
  • We also include prop1 and prop2 as jetty properties as well as system properties. Is this necessary?
  • prop1 as a jetty property is twice quoted, which is incorrect.

I think the output should be (assuming we need both system and jetty properties for prop1 and prop2):

/usr/lib/jvm/java-17-openjdk-amd64/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
-Djetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
-Dprop2='foo\'bar' \
-Dprop1='foo bar' \
--class-path  org.eclipse.jetty.xml.XmlConfiguration \
jetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
jetty.base.uri=file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
jetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
jetty.home.uri=file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
prop1='foo bar' \
prop2='foo\'bar' \
runtime.feature.alpn=true

Thus I think we need a utility quoteIfNeeded(String) method that quotes only if there are spaces, other quotes, or other special characters (need to check with shell documentation) and that is able to escape quotes when needed. This should be used for both name and value of properties in the --dry-run

@joakime do you want to take over this PR(or issue) and fix this?

@gregw
Copy link
Contributor

gregw commented Jul 7, 2023

Update. I don't think we should be using single quotes at all. From "man sh" I see:

   Single Quotes
     Enclosing characters in single quotes preserves the literal meaning of all the characters (except single quotes, making it impossible to put single-quotes in a single-quoted string).

   Double Quotes
     Enclosing characters within double quotes preserves the literal meaning of all characters except dollarsign ($), backquote (`), and backslash (\).  The backslash inside double quotes is historically weird, and serves
     to quote only the following characters:
           $ ` " \ <newline>.
     Otherwise it remains literal.

So this indicates that I'm wrong in my previous comment when I suggested we should have 'foo\'bar' as a value, since we cannot put single quotes in single quotes. So I think we should use double quotes, but note the historic "weirdness" of that!

Thus I think output should be (see new prop3):

/usr/lib/jvm/java-17-openjdk-amd64/bin/java -Djava.io.tmpdir=/tmp -Djetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
-Djetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
-Dprop2="foo'bar" \
-Dprop1="foo bar" \
-Dprop3="foo\"bar" \
--class-path  org.eclipse.jetty.xml.XmlConfiguration \
jetty.base=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
jetty.base.uri=file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home/base \
jetty.home=/home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
jetty.home.uri=file:///home/gregw/src/jetty-12.0.x/jetty-home/target/jetty-home \
prop1="foo bar" \
prop2="foo'bar" \
prop3="foo\"bar" \
runtime.feature.alpn=true

@gregw
Copy link
Contributor

gregw commented Jul 7, 2023

Note also from the bash manual:

 metacharacter
              A character that, when unquoted, separates words.  One of the following:
              |  & ; ( ) < > space tab newline

So as bash is often used as the shell, we should treat all of these characters as needing to be quoted as well.

@gregw
Copy link
Contributor

gregw commented Jul 7, 2023

@joakime I think the quote method should be something like:

    public static String quoteIfNeededForShell(String input)
    {
        if (input.codePoints().noneMatch(StringUtil::isQuotingNeededForCodePoint))
            return input; // No quoting necessary

        StringBuilder output = new StringBuilder(input.length() + 16);
        output.append('"'); // Start with a double quote
        input.codePoints().forEach(c ->
        {
            switch (c)
            {
                case '"', '\\', '`', '$' -> output.append('\\').appendCodePoint(c);
                default -> output.appendCodePoint(c);
            }
        });
        output.append('"');

        return output.toString();
    }

    public static boolean isQuotingNeededForCodePoint(int codePoint)
    {
        int type = Character.getType(codePoint);
        return switch (type)
        {
            case Character.SPACE_SEPARATOR,
                Character.LINE_SEPARATOR,
                Character.PARAGRAPH_SEPARATOR,
                Character.CONTROL,
                Character.FORMAT,
                Character.PRIVATE_USE,
                Character.SURROGATE,
                Character.OTHER_SYMBOL -> true;
            default -> codePoint == '"' ||
                codePoint == '\\' ||
                codePoint == '$' ||
                codePoint == '`' ||
                codePoint == '|' ||
                codePoint == '&' ||
                codePoint == ';' ||
                codePoint == '(' ||
                codePoint == ')' ||
                codePoint == '<' ||
                codePoint == '>' ||
                codePoint == '!' ||
                codePoint == '*';
        };
    }

@gregw
Copy link
Contributor

gregw commented Jul 10, 2023

@joakime, you have a few other tasks, so I'll start work on this one.

@sbordet sbordet closed this in 11d2a32 Jul 14, 2023
@joakime joakime deleted the jetty-10.0.x-quotedStartArgs branch September 18, 2023 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants