Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apache-maven/src/assembly/component.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ under the License.
<includes>
<include>*.cmd</include>
<include>*.conf</include>
<include>*.java</include>
</includes>
<lineEnding>dos</lineEnding>
</fileSet>
Expand Down
131 changes: 131 additions & 0 deletions apache-maven/src/assembly/maven/bin/JvmConfigParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
* Parses .mvn/jvm.config file for Windows batch scripts.
* This avoids the complexity of parsing special characters (pipes, quotes, etc.) in batch scripts.
*
* Usage: java JvmConfigParser.java <jvm.config-path> <maven-project-basedir>
*
* Outputs: Single line with space-separated quoted arguments (safe for batch scripts)
*/
public class JvmConfigParser {
public static void main(String[] args) {
if (args.length != 2) {
System.err.println("Usage: java JvmConfigParser.java <jvm.config-path> <maven-project-basedir>");
System.exit(1);
}

Path jvmConfigPath = Paths.get(args[0]);
String mavenProjectBasedir = args[1];

if (!Files.exists(jvmConfigPath)) {
// No jvm.config file - output nothing
return;
}

try (Stream<String> lines = Files.lines(jvmConfigPath, StandardCharsets.UTF_8)) {
StringBuilder result = new StringBuilder();

lines.forEach(line -> {
// Remove comments
int commentIndex = line.indexOf('#');
if (commentIndex >= 0) {
line = line.substring(0, commentIndex);
}

// Trim whitespace
line = line.trim();

// Skip empty lines
if (line.isEmpty()) {
return;
}

// Replace MAVEN_PROJECTBASEDIR placeholders
line = line.replace("${MAVEN_PROJECTBASEDIR}", mavenProjectBasedir);
line = line.replace("$MAVEN_PROJECTBASEDIR", mavenProjectBasedir);

// Parse line into individual arguments (split on spaces, respecting quotes)
List<String> parsed = parseArguments(line);

// Append each argument quoted
for (String arg : parsed) {
if (result.length() > 0) {
result.append(' ');
}
result.append('"').append(arg).append('"');
}
});

System.out.print(result.toString());
} catch (IOException e) {
System.err.println("Error reading jvm.config: " + e.getMessage());
System.exit(1);
}
}

/**
* Parse a line into individual arguments, respecting quoted strings.
* Quotes are stripped from the arguments.
*/
private static List<String> parseArguments(String line) {
List<String> args = new ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inQuotes = false;
boolean inSingleQuotes = false;

for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);

if (c == '"' && !inSingleQuotes) {
inQuotes = !inQuotes;
// Don't include the quote character itself
} else if (c == '\'' && !inQuotes) {
inSingleQuotes = !inSingleQuotes;
// Don't include the quote character itself
} else if (c == ' ' && !inQuotes && !inSingleQuotes) {
// Space outside quotes - end of argument
if (current.length() > 0) {
args.add(current.toString());
current.setLength(0);
}
} else {
current.append(c);
}
}

// Add last argument
if (current.length() > 0) {
args.add(current.toString());
}

return args;
}
}

58 changes: 38 additions & 20 deletions apache-maven/src/assembly/maven/bin/mvn
Original file line number Diff line number Diff line change
Expand Up @@ -168,28 +168,48 @@ find_file_argument_basedir() {
# concatenates all lines of a file and replaces variables
concat_lines() {
if [ -f "$1" ]; then
# First convert all CR to LF using tr
# Convert CR to LF, remove empty lines and comments, perform variable substitution,
# and escape pipe symbols for eval
tr '\r' '\n' < "$1" | \
sed -e '/^$/d' -e 's/#.*$//' | \
# Replace LF with NUL for xargs
tr '\n' '\0' | \
# Split into words and process each argument
# Use -0 with NUL to avoid special behaviour on quotes
xargs -n 1 -0 | \
while read -r arg; do
# Replace variables first
arg=$(echo "$arg" | sed \
-e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
-e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g")

echo "$arg"
done | \
tr '\n' ' '
sed \
-e "s@\${MAVEN_PROJECTBASEDIR}@$MAVEN_PROJECTBASEDIR@g" \
-e "s@\$MAVEN_PROJECTBASEDIR@$MAVEN_PROJECTBASEDIR@g" | \
awk '
{
result = ""
in_quotes = 0
for (i = 1; i <= length($0); i++) {
char = substr($0, i, 1)
if (char == "\"") {
in_quotes = !in_quotes
result = result char
} else if (char == "|" && !in_quotes) {
# Escape unquoted pipes for eval
result = result "\\|"
} else {
result = result char
}
}
# Accumulate lines with space separator
if (NR > 1) printf " "
printf "%s", result
}
END { if (NR > 0) print "" }
'
fi
}

MAVEN_PROJECTBASEDIR="`find_maven_basedir "$@"`"
MAVEN_OPTS="$MAVEN_OPTS `concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
# Read JVM config and append to MAVEN_OPTS, preserving special characters
_jvm_config="`concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config"`"
if [ -n "$_jvm_config" ]; then
if [ -n "$MAVEN_OPTS" ]; then
MAVEN_OPTS="$MAVEN_OPTS $_jvm_config"
else
MAVEN_OPTS="$_jvm_config"
fi
fi
LAUNCHER_JAR=`echo "$MAVEN_HOME"/boot/plexus-classworlds-*.jar`
LAUNCHER_CLASS=org.codehaus.plexus.classworlds.launcher.Launcher

Expand Down Expand Up @@ -239,6 +259,7 @@ handle_args() {
handle_args "$@"
MAVEN_MAIN_CLASS=${MAVEN_MAIN_CLASS:=org.apache.maven.cling.MavenCling}

# Build command string for eval
cmd="\"$JAVACMD\" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
Expand All @@ -251,13 +272,10 @@ cmd="\"$JAVACMD\" \
\"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR\" \
$LAUNCHER_CLASS \
$MAVEN_ARGS"

# Add remaining arguments with proper quoting
for arg in "$@"; do
cmd="$cmd \"$arg\""
done

# Debug: print the command that will be executed
#echo "About to execute:"
#echo "$cmd"

eval exec "$cmd"
39 changes: 8 additions & 31 deletions apache-maven/src/assembly/maven/bin/mvn.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -179,36 +179,13 @@ cd /d "%EXEC_DIR%"

if not exist "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadJvmConfig

@setlocal EnableExtensions EnableDelayedExpansion
set JVM_CONFIG_MAVEN_OPTS=
for /F "usebackq tokens=* delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do (
set "line=%%a"

rem Skip empty lines and full-line comments
echo !line! | findstr /b /r /c:"[ ]*#" >nul
if errorlevel 1 (
rem Handle end-of-line comments by taking everything before #
for /f "tokens=1* delims=#" %%i in ("!line!") do set "line=%%i"

rem Trim leading/trailing spaces while preserving spaces in quotes
set "trimmed=!line!"
for /f "tokens=* delims= " %%i in ("!trimmed!") do set "trimmed=%%i"
for /l %%i in (1,1,100) do if "!trimmed:~-1!"==" " set "trimmed=!trimmed:~0,-1!"

rem Replace MAVEN_PROJECTBASEDIR placeholders
set "trimmed=!trimmed:${MAVEN_PROJECTBASEDIR}=%MAVEN_PROJECTBASEDIR%!"
set "trimmed=!trimmed:$MAVEN_PROJECTBASEDIR=%MAVEN_PROJECTBASEDIR%!"

if not "!trimmed!"=="" (
if "!JVM_CONFIG_MAVEN_OPTS!"=="" (
set "JVM_CONFIG_MAVEN_OPTS=!trimmed!"
) else (
set "JVM_CONFIG_MAVEN_OPTS=!JVM_CONFIG_MAVEN_OPTS! !trimmed!"
)
)
)
)
@endlocal & set JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS%
rem Use Java to parse jvm.config to avoid batch script parsing issues with special characters
rem This handles pipes, quotes, and other special characters correctly
set "JVM_CONFIG_TEMP=%TEMP%\mvn-jvm-config-%RANDOM%.txt"
"%JAVACMD%" "%MAVEN_HOME%\bin\JvmConfigParser.java" "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" > "%JVM_CONFIG_TEMP%" 2>nul
rem Read the single line from temp file
set /p JVM_CONFIG_MAVEN_OPTS=<"%JVM_CONFIG_TEMP%"
del "%JVM_CONFIG_TEMP%" 2>nul

:endReadJvmConfig

Expand Down Expand Up @@ -286,4 +263,4 @@ if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause

exit /b %ERROR_CODE%
exit /b %ERROR_CODE%
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.it;

import java.nio.file.Path;
import java.util.Properties;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
* This is a test set for <a href="https://github.com/apache/maven/issues/11363">gh-11363</a>:
* Verify that pipe symbols in .mvn/jvm.config are properly handled and don't cause shell command parsing errors.
*/
public class MavenITgh11363PipeSymbolsInJvmConfigTest extends AbstractMavenIntegrationTestCase {

/**
* Verify that pipe symbols in .mvn/jvm.config are properly handled
*/
@Test
void testPipeSymbolsInJvmConfig() throws Exception {
Path basedir = extractResources("/gh-11363-pipe-symbols-jvm-config")
.getAbsoluteFile()
.toPath();

Verifier verifier = newVerifier(basedir.toString());
verifier.setForkJvm(true); // Use forked JVM to test .mvn/jvm.config processing
verifier.addCliArguments("validate");
verifier.execute();
verifier.verifyErrorFreeLog();

Properties props = verifier.loadProperties("target/pom.properties");
assertEquals("de|*.de|my.company.mirror.de", props.getProperty("project.properties.pom.prop.nonProxyHosts"));
assertEquals("value|with|pipes", props.getProperty("project.properties.pom.prop.with.pipes"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Test for MNG-11363: Maven 4 fails to parse pipe symbols in .mvn/jvm.config
-Dhttp.nonProxyHosts=de|*.de|my.company.mirror.de
-Dprop.with.pipes="value|with|pipes"
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<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">
<modelVersion>4.0.0</modelVersion>

<groupId>org.apache.maven.its.mng11363</groupId>
<artifactId>test</artifactId>
<version>1.0</version>

<name>Maven Integration Test :: MNG-11363</name>
<description>Verify that JVM args can contain pipe symbols in .mvn/jvm.config.</description>

<properties>
<pom.prop.nonProxyHosts>${http.nonProxyHosts}</pom.prop.nonProxyHosts>
<pom.prop.with.pipes>${prop.with.pipes}</pom.prop.with.pipes>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.its.plugins</groupId>
<artifactId>maven-it-plugin-expression</artifactId>
<version>2.1-SNAPSHOT</version>
<executions>
<execution>
<id>test</id>
<goals>
<goal>eval</goal>
</goals>
<phase>validate</phase>
<configuration>
<outputFile>target/pom.properties</outputFile>
<expressions>
<expression>project/properties</expression>
</expressions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Loading