diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml index 4d75c9a38ca8..5f55a310c8bd 100644 --- a/apache-maven/src/assembly/component.xml +++ b/apache-maven/src/assembly/component.xml @@ -68,6 +68,7 @@ under the License. *.cmd *.conf + *.java dos diff --git a/apache-maven/src/assembly/maven/bin/JvmConfigParser.java b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java new file mode 100644 index 000000000000..1d6360aac94a --- /dev/null +++ b/apache-maven/src/assembly/maven/bin/JvmConfigParser.java @@ -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 + * + * 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 "); + 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 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 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 parseArguments(String line) { + List 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; + } +} + diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn index 8559d47af557..7ec4b77cb982 100755 --- a/apache-maven/src/assembly/maven/bin/mvn +++ b/apache-maven/src/assembly/maven/bin/mvn @@ -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 @@ -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 \ @@ -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" diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd index a3e8600df3d1..43c2be68c199 100644 --- a/apache-maven/src/assembly/maven/bin/mvn.cmd +++ b/apache-maven/src/assembly/maven/bin/mvn.cmd @@ -177,38 +177,33 @@ cd /d "%EXEC_DIR%" :endDetectBaseDir +rem Initialize JVM_CONFIG_MAVEN_OPTS to empty to avoid inheriting from environment +set JVM_CONFIG_MAVEN_OPTS= + 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 +rem Use random temp directory to avoid conflicts between different Maven versions +set "JVM_CONFIG_PARSER_DIR=%TEMP%\mvn-jvm-parser-%RANDOM%-%RANDOM%" +mkdir "%JVM_CONFIG_PARSER_DIR%" +set "JVM_CONFIG_TEMP=%TEMP%\mvn-jvm-config-%RANDOM%.txt" +set "JVM_CONFIG_ERR=%TEMP%\mvn-jvm-config-err-%RANDOM%.txt" +"%JAVACMD:java.exe=javac.exe%" -d "%JVM_CONFIG_PARSER_DIR%" "%MAVEN_HOME%\bin\JvmConfigParser.java" >nul 2>&1 +"%JAVACMD%" -cp "%JVM_CONFIG_PARSER_DIR%" JvmConfigParser "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" "%MAVEN_PROJECTBASEDIR%" > "%JVM_CONFIG_TEMP%" 2> "%JVM_CONFIG_ERR%" +rem Read the single line from temp file +set /p JVM_CONFIG_MAVEN_OPTS=<"%JVM_CONFIG_TEMP%" + +rem Debug output to file for IT verification +echo JVM_CONFIG_MAVEN_OPTS=%JVM_CONFIG_MAVEN_OPTS% > "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt" +echo MAVEN_OPTS=%MAVEN_OPTS% >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt" +echo PARSER_STDERR: >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt" +type "%JVM_CONFIG_ERR%" >> "%MAVEN_PROJECTBASEDIR%\mvn-debug.txt" 2>nul + +rem Cleanup temp files and directory +del "%JVM_CONFIG_TEMP%" 2>nul +del "%JVM_CONFIG_ERR%" 2>nul +rmdir /s /q "%JVM_CONFIG_PARSER_DIR%" 2>nul :endReadJvmConfig @@ -286,4 +281,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% \ No newline at end of file diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java new file mode 100644 index 000000000000..95e0bba2d85e --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh11363PipeSymbolsInJvmConfigTest.java @@ -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 gh-11363: + * 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")); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config new file mode 100644 index 000000000000..fa129e3da219 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/.mvn/jvm.config @@ -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" diff --git a/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml new file mode 100644 index 000000000000..52f90ad94181 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-11363-pipe-symbols-jvm-config/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + org.apache.maven.its.mng11363 + test + 1.0 + + Maven Integration Test :: MNG-11363 + Verify that JVM args can contain pipe symbols in .mvn/jvm.config. + + + ${http.nonProxyHosts} + ${prop.with.pipes} + + + + + + org.apache.maven.its.plugins + maven-it-plugin-expression + 2.1-SNAPSHOT + + + test + + eval + + validate + + target/pom.properties + + project/properties + + + + + + + +