Skip to content

Commit 465eef5

Browse files
committed
Fix pipe symbol parsing in .mvn/jvm.config
Fixes #11363
1 parent d5076b7 commit 465eef5

File tree

5 files changed

+229
-38
lines changed

5 files changed

+229
-38
lines changed

apache-maven/src/assembly/maven/bin/mvn

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -168,28 +168,45 @@ find_file_argument_basedir() {
168168
# concatenates all lines of a file and replaces variables
169169
concat_lines() {
170170
if [ -f "$1" ]; then
171-
# First convert all CR to LF using tr
172-
tr '\r' '\n' < "$1" | \
173-
sed -e '/^$/d' -e 's/#.*$//' | \
174-
# Replace LF with NUL for xargs
175-
tr '\n' '\0' | \
176-
# Split into words and process each argument
177-
# Use -0 with NUL to avoid special behaviour on quotes
178-
xargs -n 1 -0 | \
179-
while read -r arg; do
180-
# Replace variables first
181-
arg=$(echo "$arg" | sed \
182-
-e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
183-
-e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g")
184-
185-
echo "$arg"
186-
done | \
187-
tr '\n' ' '
171+
# First pass: clean the file (convert CR to LF, remove empty lines and comments)
172+
# and perform variable substitution using sed
173+
tr '\r' '\n' < "$1" | sed -e '/^$/d' -e 's/#.*$//' | sed \
174+
-e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
175+
-e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g" | \
176+
# Second pass: escape unquoted pipe symbols using awk
177+
awk '
178+
{
179+
result = ""
180+
in_quotes = 0
181+
for (i = 1; i <= length($0); i++) {
182+
char = substr($0, i, 1)
183+
if (char == "\"") {
184+
in_quotes = !in_quotes
185+
} else if (char == "|" && !in_quotes) {
186+
char = "\\|"
187+
}
188+
result = result char
189+
}
190+
191+
# Accumulate lines with space separator
192+
if (NR > 1) printf " "
193+
printf "%s", result
194+
}
195+
END { print "" }
196+
'
188197
fi
189198
}
190199

191200
MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`"
192-
MAVEN_OPTS="$MAVEN_OPTS `concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
201+
# Read JVM config and append to MAVEN_OPTS, preserving special characters
202+
_jvm_config="`concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
203+
if [ -n "$_jvm_config" ]; then
204+
if [ -n "$MAVEN_OPTS" ]; then
205+
MAVEN_OPTS="$MAVEN_OPTS $_jvm_config"
206+
else
207+
MAVEN_OPTS="$_jvm_config"
208+
fi
209+
fi
193210
LAUNCHER_JAR=`echo "$MAVEN_HOME"/boot/plexus-classworlds-*.jar`
194211
LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher
195212

@@ -236,28 +253,43 @@ handle_args() {
236253
done
237254
}
238255

256+
257+
239258
handle_args "$@"
240259
MAVEN_MAIN_CLASS=${MAVEN_MAIN_CLASS:=org.apache.maven.cling.MavenCling}
241260

242-
cmd="\"$JAVACMD\" \
243-
$MAVEN_OPTS \
244-
$MAVEN_DEBUG_OPTS \
245-
--enable-native-access=ALL-UNNAMED \
246-
-classpath \"$LAUNCHER_JAR\" \
247-
\"-Dclassworlds.conf=$CLASSWORLDS_CONF\" \
248-
\"-Dmaven.home=$MAVEN_HOME\" \
249-
\"-Dmaven.mainClass=$MAVEN_MAIN_CLASS\" \
250-
\"-Dlibrary.jline.path=${MAVEN_HOME}/lib/jline-native\" \
251-
\"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\" \
252-
$LAUNCHER_CLASS \
253-
$MAVEN_ARGS"
254-
# Add remaining arguments with proper quoting
261+
# Build the command with proper quoting for pipe symbols
262+
cmd="exec \"$JAVACMD\""
263+
264+
# Add MAVEN_OPTS if present
265+
if [ -n "$MAVEN_OPTS" ]; then
266+
cmd="$cmd $MAVEN_OPTS"
267+
fi
268+
269+
# Add MAVEN_DEBUG_OPTS if present
270+
if [ -n "$MAVEN_DEBUG_OPTS" ]; then
271+
cmd="$cmd $MAVEN_DEBUG_OPTS"
272+
fi
273+
274+
# Add fixed arguments
275+
cmd="$cmd --enable-native-access=ALL-UNNAMED"
276+
cmd="$cmd -classpath \"$LAUNCHER_JAR\""
277+
cmd="$cmd \"-Dclassworlds.conf=$CLASSWORLDS_CONF\""
278+
cmd="$cmd \"-Dmaven.home=$MAVEN_HOME\""
279+
cmd="$cmd \"-Dmaven.mainClass=$MAVEN_MAIN_CLASS\""
280+
cmd="$cmd \"-Dlibrary.jline.path=${MAVEN_HOME}/lib/jline-native\""
281+
cmd="$cmd \"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\""
282+
cmd="$cmd $LAUNCHER_CLASS"
283+
284+
# Add MAVEN_ARGS if present
285+
if [ -n "$MAVEN_ARGS" ]; then
286+
cmd="$cmd $MAVEN_ARGS"
287+
fi
288+
289+
# Add remaining command line arguments
255290
for arg in "$@"; do
256-
cmd="$cmd \"$arg\""
291+
cmd="$cmd \"$arg\""
257292
done
258293

259-
# Debug: print the command that will be executed
260-
#echo "About to execute:"
261-
#echo "$cmd"
262-
263-
eval exec "$cmd"
294+
# Execute the command using eval to properly handle quoted arguments
295+
eval "$cmd"

apache-maven/src/assembly/maven/bin/mvn.cmd

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ for /F "usebackq tokens=* delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.conf
199199
set "trimmed=!trimmed:${MAVEN_PROJECTBASEDIR}=%MAVEN_PROJECTBASEDIR%!"
200200
set "trimmed=!trimmed:$MAVEN_PROJECTBASEDIR=%MAVEN_PROJECTBASEDIR%!"
201201

202+
rem Wrap in quotes if contains pipe and not already quoted
203+
call :quoteIfNeeded "!trimmed!" trimmed
204+
202205
if not "!trimmed!"=="" (
203206
if "!JVM_CONFIG_MAVEN_OPTS!"=="" (
204207
set "JVM_CONFIG_MAVEN_OPTS=!trimmed!"
@@ -208,7 +211,7 @@ for /F "usebackq tokens=* delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.conf
208211
)
209212
)
210213
)
211-
@endlocal & set JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%
214+
@endlocal & set "JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%"
212215

213216
:endReadJvmConfig
214217

@@ -287,3 +290,44 @@ if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
287290
if "%MAVEN_BATCH_PAUSE%"=="on" pause
288291

289292
exit /b %ERROR_CODE%
293+
294+
rem Subroutine to wrap argument value in quotes if it contains pipe and is not already quoted
295+
:quoteIfNeeded
296+
setlocal EnableDelayedExpansion
297+
set "input=%~1"
298+
set "output=!input!"
299+
300+
rem Check if input contains pipe symbol
301+
echo !input! | findstr /C:"|" >nul
302+
if errorlevel 1 goto noQuotingNeeded
303+
304+
rem Check if it's a -D property and the value part is not quoted
305+
echo !input! | findstr /B /C:"-D" >nul
306+
if errorlevel 1 goto wrapEntireArg
307+
308+
rem It's a -D property, check if value is already quoted
309+
set "afterEquals=!input:*==!"
310+
if "!afterEquals!"=="!input!" goto wrapEntireArg
311+
set "firstChar=!afterEquals:~0,1!"
312+
set "lastChar=!afterEquals:~-1!"
313+
if "!firstChar!"=="^"" if "!lastChar!"=="^"" goto noQuotingNeeded
314+
315+
rem Extract property name and value, then wrap value in quotes
316+
for /f "tokens=1* delims==" %%a in ("!input!") do (
317+
set "propName=%%a"
318+
set "propValue=%%b"
319+
)
320+
set "output=!propName!="!propValue!""
321+
goto noQuotingNeeded
322+
323+
:wrapEntireArg
324+
rem Not a -D property or doesn't have =, check if already quoted
325+
set "firstChar=!input:~0,1!"
326+
set "lastChar=!input:~-1!"
327+
if "!firstChar!"=="^"" if "!lastChar!"=="^"" goto noQuotingNeeded
328+
rem Wrap entire argument in quotes
329+
set "output="!input!""
330+
331+
:noQuotingNeeded
332+
endlocal & set "%~2=%output%"
333+
goto :eof
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.it;
20+
21+
import java.nio.file.Path;
22+
import java.util.Properties;
23+
24+
import org.junit.jupiter.api.Test;
25+
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
28+
/**
29+
* This is a test set for <a href="https://github.com/apache/maven/issues/11363">gh-11363</a>:
30+
* Verify that pipe symbols in .mvn/jvm.config are properly handled and don't cause shell command parsing errors.
31+
*/
32+
public class MavenITgh11363PipeSymbolsInJvmConfigTest extends AbstractMavenIntegrationTestCase {
33+
34+
/**
35+
* Verify that pipe symbols in .mvn/jvm.config are properly handled
36+
*/
37+
@Test
38+
void testPipeSymbolsInJvmConfig() throws Exception {
39+
Path basedir = extractResources("/gh-11363-pipe-symbols-jvm-config")
40+
.getAbsoluteFile()
41+
.toPath();
42+
43+
Verifier verifier = newVerifier(basedir.toString());
44+
verifier.setForkJvm(true); // Use forked JVM to test .mvn/jvm.config processing
45+
verifier.addCliArguments("validate");
46+
verifier.execute();
47+
verifier.verifyErrorFreeLog();
48+
49+
Properties props = verifier.loadProperties("target/pom.properties");
50+
assertEquals("de|*.de|my.company.mirror.de", props.getProperty("project.properties.pom.prop.nonProxyHosts"));
51+
assertEquals("value|with|pipes", props.getProperty("project.properties.pom.prop.with.pipes"));
52+
}
53+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Test for MNG-11363: Maven 4 fails to parse pipe symbols in .mvn/jvm.config
2+
-Dhttp.nonProxyHosts=de|*.de|my.company.mirror.de
3+
-Dprop.with.pipes="value|with|pipes"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
23+
<groupId>org.apache.maven.its.mng11363</groupId>
24+
<artifactId>test</artifactId>
25+
<version>1.0</version>
26+
27+
<name>Maven Integration Test :: MNG-11363</name>
28+
<description>Verify that JVM args can contain pipe symbols in .mvn/jvm.config.</description>
29+
30+
<properties>
31+
<pom.prop.nonProxyHosts>${http.nonProxyHosts}</pom.prop.nonProxyHosts>
32+
<pom.prop.with.pipes>${prop.with.pipes}</pom.prop.with.pipes>
33+
</properties>
34+
35+
<build>
36+
<plugins>
37+
<plugin>
38+
<groupId>org.apache.maven.its.plugins</groupId>
39+
<artifactId>maven-it-plugin-expression</artifactId>
40+
<version>2.1-SNAPSHOT</version>
41+
<executions>
42+
<execution>
43+
<id>test</id>
44+
<goals>
45+
<goal>eval</goal>
46+
</goals>
47+
<phase>validate</phase>
48+
<configuration>
49+
<outputFile>target/pom.properties</outputFile>
50+
<expressions>
51+
<expression>project/properties</expression>
52+
</expressions>
53+
</configuration>
54+
</execution>
55+
</executions>
56+
</plugin>
57+
</plugins>
58+
</build>
59+
</project>

0 commit comments

Comments
 (0)