From d5dcf0ef000cbbc4808ee57af6a483f51d07390c Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 27 Oct 2023 11:52:45 +0200 Subject: [PATCH] Support "--" end of options in SimpleCommandLineArgsParser Prior to this commit, the `SimpleCommandLineArgsParser` would reject "--" arguments as invalid. As reported by the community, the POSIX utility conventions (Guideline 10) state that > The first -- argument that is not an option-argument should be > accepted as a delimiter indicating the end of options. > Any following arguments should be treated as operands, even if they > begin with the '-' character. This commit updates `SimpleCommandLineArgsParser` to not reject "--" arguments and instead to consider remaining arguments as non-optional. Fixes gh-21949 --- .../core/env/SimpleCommandLineArgsParser.java | 33 ++++++++++++------- .../env/SimpleCommandLineArgsParserTests.java | 17 ++++++---- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java index bcf8d071604c..08b01439834e 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java +++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,13 @@ * without spaces by an equals sign ("="). The value may optionally be * an empty string. * + *

This parser supports the POSIX "end of options" delimiter, meaning that + * any {@code "--"} (empty option name) in the command signals that all remaining + * arguments will non-optional. For example, here {@code "--opt=ignored"} is considered + * as a non-optional argument. + *

+ * --foo=bar -- --opt=ignored
+ * *

Valid examples of option arguments

*
  * --foo
@@ -53,6 +60,7 @@
  *
  * @author Chris Beams
  * @author Sam Brannen
+ * @author Brian Clozel
  * @since 3.1
  */
 class SimpleCommandLineArgsParser {
@@ -65,23 +73,26 @@ class SimpleCommandLineArgsParser {
 	 */
 	public CommandLineArgs parse(String... args) {
 		CommandLineArgs commandLineArgs = new CommandLineArgs();
+		boolean endOfOptions = false;
 		for (String arg : args) {
-			if (arg.startsWith("--")) {
+			if (!endOfOptions && arg.startsWith("--")) {
 				String optionText = arg.substring(2);
-				String optionName;
-				String optionValue = null;
 				int indexOfEqualsSign = optionText.indexOf('=');
 				if (indexOfEqualsSign > -1) {
-					optionName = optionText.substring(0, indexOfEqualsSign);
-					optionValue = optionText.substring(indexOfEqualsSign + 1);
+					String optionName = optionText.substring(0, indexOfEqualsSign);
+					String optionValue = optionText.substring(indexOfEqualsSign + 1);
+					if (optionName.isEmpty()) {
+						throw new IllegalArgumentException("Invalid argument syntax: " + arg);
+					}
+					commandLineArgs.addOptionArg(optionName, optionValue);
 				}
-				else {
-					optionName = optionText;
+				else if (!optionText.isEmpty()){
+					commandLineArgs.addOptionArg(optionText, null);
 				}
-				if (optionName.isEmpty()) {
-					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
+				else {
+					// '--' End of options delimiter, all remaining args must be non-optional
+					endOfOptions = true;
 				}
-				commandLineArgs.addOptionArg(optionName, optionValue);
 			}
 			else {
 				commandLineArgs.addNonOptionArg(arg);
diff --git a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java
index 4e3f186f87b0..4331a4af91e2 100644
--- a/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java
+++ b/spring-core/src/test/java/org/springframework/core/env/SimpleCommandLineArgsParserTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
  *
  * @author Chris Beams
  * @author Sam Brannen
+ * @author Brian Clozel
  */
 class SimpleCommandLineArgsParserTests {
 
@@ -66,11 +67,6 @@ void withMixOfOptionsHavingValueAndOptionsHavingNoValue() {
 		assertThat(args.getOptionValues("o3")).isNull();
 	}
 
-	@Test
-	void withEmptyOptionText() {
-		assertThatIllegalArgumentException().isThrownBy(() -> parser.parse("--"));
-	}
-
 	@Test
 	void withEmptyOptionName() {
 		assertThatIllegalArgumentException().isThrownBy(() -> parser.parse("--=v1"));
@@ -112,4 +108,13 @@ void assertNonOptionArgsIsUnmodifiable() {
 				args.getNonOptionArgs().add("foo"));
 	}
 
+	@Test
+	void supportsEndOfOptionsDelimiter() {
+		CommandLineArgs args = parser.parse("--o1=v1", "--", "--o2=v2");
+		assertThat(args.containsOption("o1")).isTrue();
+		assertThat(args.containsOption("o2")).isFalse();
+		assertThat(args.getOptionValues("o1")).containsExactly("v1");
+		assertThat(args.getNonOptionArgs()).contains("--o2=v2");
+	}
+
 }