diff --git a/aliyun-java-sdk-core/pom.xml b/aliyun-java-sdk-core/pom.xml
index e93f61e16d..73467634da 100644
--- a/aliyun-java-sdk-core/pom.xml
+++ b/aliyun-java-sdk-core/pom.xml
@@ -140,6 +140,7 @@
org.ini4j
ini4j
0.5.4
+ test
org.slf4j
diff --git a/aliyun-java-sdk-core/src/main/java/com/aliyuncs/auth/ProfileCredentialsProvider.java b/aliyun-java-sdk-core/src/main/java/com/aliyuncs/auth/ProfileCredentialsProvider.java
index de0601efc7..c32398046e 100644
--- a/aliyun-java-sdk-core/src/main/java/com/aliyuncs/auth/ProfileCredentialsProvider.java
+++ b/aliyun-java-sdk-core/src/main/java/com/aliyuncs/auth/ProfileCredentialsProvider.java
@@ -2,23 +2,20 @@
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.utils.AuthUtils;
+import com.aliyuncs.utils.ProfileUtils;
import com.aliyuncs.utils.StringUtils;
-import org.ini4j.Profile;
-import org.ini4j.Wini;
-
-import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class ProfileCredentialsProvider implements AlibabaCloudCredentialsProvider {
- private static volatile Wini ini;
+ private static volatile Map> ini;
- private static Wini getIni(String filePath) throws IOException {
+ private static Map> getIni(String filePath) throws IOException {
if (null == ini) {
synchronized (ProfileCredentialsProvider.class) {
if (null == ini) {
- ini = new Wini(new File(filePath));
+ ini = ProfileUtils.parseFile(filePath);
}
}
}
@@ -34,7 +31,7 @@ public AlibabaCloudCredentials getCredentials() throws ClientException {
if (filePath.isEmpty()) {
throw new ClientException("The specified credentials file is empty");
}
- Wini ini;
+ Map> ini;
try {
ini = getIni(filePath);
} catch (IOException e) {
@@ -49,16 +46,14 @@ public AlibabaCloudCredentials getCredentials() throws ClientException {
return createCredential(clientConfig, credentialsProviderFactory);
}
- private Map> loadIni(Wini ini) {
+ private Map> loadIni(Map> ini) {
Map> client = new HashMap>();
- boolean enable;
- for (Map.Entry clientType : ini.entrySet()) {
- enable = clientType.getValue().get(AuthConstant.INI_ENABLE, boolean.class);
- if (enable) {
+ String enable;
+ for (Map.Entry> clientType : ini.entrySet()) {
+ enable = clientType.getValue().get(AuthConstant.INI_ENABLE);
+ if (Boolean.parseBoolean(enable)) {
Map clientConfig = new HashMap();
- for (Map.Entry enabledClient : clientType.getValue().entrySet()) {
- clientConfig.put(enabledClient.getKey(), enabledClient.getValue());
- }
+ clientConfig.putAll(clientType.getValue());
client.put(clientType.getKey(), clientConfig);
}
}
diff --git a/aliyun-java-sdk-core/src/main/java/com/aliyuncs/utils/ProfileUtils.java b/aliyun-java-sdk-core/src/main/java/com/aliyuncs/utils/ProfileUtils.java
new file mode 100644
index 0000000000..41eecbf092
--- /dev/null
+++ b/aliyun-java-sdk-core/src/main/java/com/aliyuncs/utils/ProfileUtils.java
@@ -0,0 +1,194 @@
+package com.aliyuncs.utils;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Only for internal use within the package, do not use it arbitrarily, backward compatibility and sustainability cannot be guaranteed.
+ */
+public class ProfileUtils {
+ private final static Log log = LogFactory.getLog(ProfileUtils.class);
+ private static final Pattern EMPTY_LINE = Pattern.compile("^[\t ]*$");
+
+ public static Map> parseFile(String profilePath) throws IOException {
+ return parseFile(new FileReader(profilePath));
+ }
+
+ static Map> parseFile(Reader input) throws IOException {
+ ParserProgress progress = new ParserProgress();
+ BufferedReader profileReader = null;
+ try {
+ profileReader = new BufferedReader(input);
+ String line;
+ while ((line = profileReader.readLine()) != null) {
+ parseLine(progress, line);
+ }
+ } finally {
+ if (profileReader != null) {
+ profileReader.close();
+ }
+ }
+ return progress.profiles;
+ }
+
+ private static void parseLine(ParserProgress progress, String line) {
+ ++progress.currentLineNumber;
+ if (!EMPTY_LINE.matcher(line).matches() && !(line.startsWith("#") || line.startsWith(";"))) {
+ if (isSectionDefinitionLine(line)) {
+ readSectionDefinitionLine(progress, line);
+ } else if (line.startsWith("\t")) {
+ readPropertyContinuationLine(progress, line);
+ } else {
+ readPropertyDefinitionLine(progress, line);
+ }
+ }
+ }
+
+ private static void readSectionDefinitionLine(ParserProgress progress, String line) {
+ String lineWithoutComments = removeTrailingComments(line, "#", ";");
+ String lineWithoutWhitespace = lineWithoutComments.trim();
+
+ if (!lineWithoutWhitespace.endsWith("]")) {
+ throw new IllegalArgumentException(String.format("Section definition must end with ']' on line %s: %s", progress.currentLineNumber, line));
+ }
+
+ String lineWithoutBrackets = lineWithoutWhitespace.substring(1, lineWithoutWhitespace.length() - 1);
+ String profileName = lineWithoutBrackets.trim();
+ if (profileName.isEmpty()) {
+ progress.ignoringCurrentProfile = true;
+ return;
+ }
+ progress.currentProfileBeingRead = profileName;
+ progress.currentPropertyBeingRead = null;
+ progress.ignoringCurrentProfile = false;
+ if (!progress.profiles.containsKey(profileName)) {
+ progress.profiles.put(profileName, new LinkedHashMap());
+ }
+ }
+
+ private static void readPropertyDefinitionLine(ParserProgress progress, String line) {
+ // Invalid profile, ignore its properties
+ if (progress.ignoringCurrentProfile) {
+ return;
+ }
+ if (progress.currentProfileBeingRead == null) {
+ // throw new IllegalArgumentException(String.format("Expected a profile definition on line %s", progress.currentLineNumber));
+ // To be consistent with ini4j's behavior
+ progress.currentProfileBeingRead = "?";
+ if (!progress.profiles.containsKey(progress.currentProfileBeingRead)) {
+ progress.profiles.put(progress.currentProfileBeingRead, new LinkedHashMap());
+ }
+ }
+
+ // Comments with property must have whitespace before them, or they will be considered part of the value
+ String lineWithoutComments = removeTrailingComments(line, " #", " ;", "\t#", "\t;");
+ String lineWithoutWhitespace = lineWithoutComments.trim();
+ Property property = parsePropertyDefinition(progress, lineWithoutWhitespace);
+
+ if (progress.profiles.get(progress.currentProfileBeingRead).containsKey(property.key())) {
+ log.warn("Duplicate property '" + property.key() + "' detected on line " + progress.currentLineNumber +
+ ". The later one in the file will be used.");
+ }
+
+ progress.currentPropertyBeingRead = property.key();
+
+ progress.profiles.get(progress.currentProfileBeingRead).put(property.key(), property.value());
+ }
+
+ private static void readPropertyContinuationLine(ParserProgress progress, String line) {
+ // Invalid profile, ignore its properties
+ if (progress.ignoringCurrentProfile) {
+ return;
+ }
+ if (progress.currentProfileBeingRead == null) {
+ // throw new IllegalArgumentException(String.format("Expected a profile definition on line %s", progress.currentLineNumber));
+ // To be consistent with ini4j's behavior
+ progress.currentProfileBeingRead = "?";
+ if (!progress.profiles.containsKey(progress.currentProfileBeingRead)) {
+ progress.profiles.put(progress.currentProfileBeingRead, new LinkedHashMap());
+ }
+ }
+
+ // Comments are not removed on property continuation lines. They're considered part of the value.
+ line = line.trim();
+ Map profileProperties = progress.profiles.get(progress.currentProfileBeingRead);
+
+ String currentPropertyValue = profileProperties.get(progress.currentPropertyBeingRead);
+ String newPropertyValue = currentPropertyValue + "\n" + line;
+ profileProperties.put(progress.currentPropertyBeingRead, newPropertyValue);
+ }
+
+ private static Property parsePropertyDefinition(ParserProgress progress, String line) {
+ int firstEqualsLocation = line.indexOf('=');
+ String propertyKey = null;
+ String propertyValue = null;
+ if (firstEqualsLocation == -1) {
+ // throw new IllegalArgumentException(String.format("Expected an '=' sign defining a property on line %s", progress.currentLineNumber));
+ // To be consistent with ini4j's behavior
+ propertyKey = line.trim();
+ } else {
+ propertyKey = line.substring(0, firstEqualsLocation).trim();
+ propertyValue = line.substring(firstEqualsLocation + 1).trim();
+ }
+
+ if (propertyKey.isEmpty()) {
+ throw new IllegalArgumentException(String.format("Property did not have a name on line %s", progress.currentLineNumber));
+ }
+
+ return new Property(propertyKey, propertyValue);
+ }
+
+ private static boolean isSectionDefinitionLine(String line) {
+ return line.trim().startsWith("[");
+ }
+
+ private static String removeTrailingComments(String line, String... commentPatterns) {
+ int earliestMatchIndex = line.length();
+ for (String pattern : commentPatterns) {
+ int index = line.indexOf(pattern);
+ if (index >= 0 && index < earliestMatchIndex) {
+ earliestMatchIndex = index;
+ }
+ }
+ return line.substring(0, earliestMatchIndex);
+ }
+
+ private static final class ParserProgress {
+ private int currentLineNumber;
+ private String currentProfileBeingRead;
+ private String currentPropertyBeingRead;
+ private boolean ignoringCurrentProfile;
+ private final Map> profiles;
+
+ private ParserProgress() {
+ this.currentLineNumber = 0;
+ this.currentProfileBeingRead = null;
+ this.currentPropertyBeingRead = null;
+ this.ignoringCurrentProfile = false;
+ this.profiles = new LinkedHashMap>();
+ }
+ }
+
+ private static final class Property {
+ private final Key key;
+ private final Value value;
+
+ private Property(Key key, Value value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public Key key() {
+ return this.key;
+ }
+
+ public Value value() {
+ return this.value;
+ }
+ }
+}
diff --git a/aliyun-java-sdk-core/src/test/java/com/aliyuncs/auth/ProfileCredentialsProviderTest.java b/aliyun-java-sdk-core/src/test/java/com/aliyuncs/auth/ProfileCredentialsProviderTest.java
index a269ff085f..81d747b65a 100644
--- a/aliyun-java-sdk-core/src/test/java/com/aliyuncs/auth/ProfileCredentialsProviderTest.java
+++ b/aliyun-java-sdk-core/src/test/java/com/aliyuncs/auth/ProfileCredentialsProviderTest.java
@@ -2,7 +2,6 @@
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.utils.AuthUtils;
-import org.ini4j.Wini;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
@@ -200,8 +199,8 @@ public void getIniTest() throws NoSuchMethodException, InvocationTargetException
getIni.setAccessible(true);
String file = ProfileCredentialsProviderTest.class.getClassLoader().
getResource("configTest.ini").getPath();
- Wini firstIni = (Wini) getIni.invoke(profileCredentialsProvider, file);
- Wini secondIni = (Wini) getIni.invoke(profileCredentialsProvider, file);
+ Map> firstIni = (Map>) getIni.invoke(profileCredentialsProvider, file);
+ Map> secondIni = (Map>) getIni.invoke(profileCredentialsProvider, file);
Assert.assertTrue(firstIni.equals(secondIni));
}
}
diff --git a/aliyun-java-sdk-core/src/test/java/com/aliyuncs/utils/ProfileUtilsTest.java b/aliyun-java-sdk-core/src/test/java/com/aliyuncs/utils/ProfileUtilsTest.java
new file mode 100644
index 0000000000..be1cdd44ee
--- /dev/null
+++ b/aliyun-java-sdk-core/src/test/java/com/aliyuncs/utils/ProfileUtilsTest.java
@@ -0,0 +1,162 @@
+package com.aliyuncs.utils;
+
+import org.ini4j.Wini;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Map;
+
+public class ProfileUtilsTest {
+ @Test
+ public void testProfile() throws IOException {
+ new ProfileUtils();
+ String context = "[profile1]\n"
+ + ";comment\n"
+ + "#comment\n"
+ + "enable = false #comment\n"
+ + "[profile2]\n"
+ + "region = cn-hangzhou#comment\n"
+ + "[default]\n"
+ + "default_property = property1 \t\n\n"
+ + "[default]\n"
+ + "default_property = property2\n"
+ + "[profile3]\n"
+ + "int = 1\n"
+ + " int = 2\n"
+ + "int = \n"
+ + "\t3\n"
+ + "str = #comment\n"
+ + "\ttest\n";
+ Wini ini = new Wini(new StringReader(context));
+ Map> iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(4, ini.size());
+ Assert.assertEquals(4, iniMap.size());
+ Assert.assertEquals("false #comment", ini.get("profile1").get("enable"));
+ Assert.assertEquals("false", iniMap.get("profile1").get("enable"));
+ Assert.assertEquals(ini.get("profile2").get("region"), iniMap.get("profile2").get("region"));
+ Assert.assertEquals(ini.get("default").get("default_property"), iniMap.get("default").get("default_property"));
+ Assert.assertEquals("\n3", iniMap.get("profile3").get("int"));
+ Assert.assertEquals("\ntest", iniMap.get("profile3").get("str"));
+
+ context = "[profile1]\n"
+ + "enable = false\n"
+ + "[profile2]\n"
+ + "enable = true\n"
+ + "[profile3]\n"
+ + "enable = null\n"
+ + "[profile4]\n"
+ + "enable = 1\n"
+ + "[profile5]\n"
+ + "enable = False\n"
+ + "[profile6]\n"
+ + "enable = True\n"
+ + "[profile7]\n"
+ + "enable =\n";
+ ini = new Wini(new StringReader(context));
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(7, ini.size());
+ Assert.assertEquals(7, iniMap.size());
+ Assert.assertEquals(false, ini.get("profile1").get("enable", Boolean.class));
+ Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile1").get("enable")));
+ Assert.assertEquals(true, ini.get("profile2").get("enable", Boolean.class));
+ Assert.assertEquals(true, Boolean.parseBoolean(iniMap.get("profile2").get("enable")));
+ Assert.assertEquals(false, ini.get("profile3").get("enable", Boolean.class));
+ Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile3").get("enable")));
+ Assert.assertEquals(false, ini.get("profile4").get("enable", Boolean.class));
+ Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile4").get("enable")));
+ Assert.assertEquals(false, ini.get("profile5").get("enable", Boolean.class));
+ Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile5").get("enable")));
+ Assert.assertEquals(true, ini.get("profile6").get("enable", Boolean.class));
+ Assert.assertEquals(true, Boolean.parseBoolean(iniMap.get("profile6").get("enable")));
+ Assert.assertEquals(false, ini.get("profile7").get("enable", Boolean.class));
+ Assert.assertEquals(false, Boolean.parseBoolean(iniMap.get("profile7").get("enable")));
+
+ context = "[invalid\n"
+ + "enable = false\n";
+ try {
+ new Wini(new StringReader(context));
+ Assert.fail();
+ } catch (Exception e) {
+ Assert.assertTrue(e.getMessage().contains("parse error"));
+ }
+ try {
+ ProfileUtils.parseFile(new StringReader(context));
+ Assert.fail();
+ } catch (Exception e) {
+ Assert.assertEquals("Section definition must end with ']' on line 1: [invalid", e.getMessage());
+ }
+
+ context = "[ ]\n"
+ + "enable = false\n"
+ + "str = \n"
+ + "\ttest\n";
+ ;
+ try {
+ new Wini(new StringReader(context));
+ Assert.fail();
+ } catch (Exception e) {
+ Assert.assertTrue(e.getMessage().contains("parse error"));
+ }
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(0, iniMap.size());
+
+ context = "[profile1]\n"
+ + "[profile2]\n";
+ ini = new Wini(new StringReader(context));
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(2, ini.size());
+ Assert.assertEquals(2, iniMap.size());
+ Assert.assertEquals(ini.get("profile1").size(), iniMap.get("profile1").size());
+ Assert.assertEquals(ini.get("profile2").size(), iniMap.get("profile2").size());
+
+ context = "enable=true\n"
+ + "key=value\n"
+ + "str = \n"
+ + "\ttest\n";
+ ini = new Wini(new StringReader(context));
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(1, ini.size());
+ Assert.assertEquals(1, iniMap.size());
+ Assert.assertEquals(4, ini.get("?").size());
+ Assert.assertEquals(3, iniMap.get("?").size());
+ Assert.assertEquals("true", ini.get("?").get("enable"));
+ Assert.assertEquals("true", iniMap.get("?").get("enable"));
+ Assert.assertEquals("value", ini.get("?").get("key"));
+ Assert.assertEquals("value", iniMap.get("?").get("key"));
+
+ context = "\ttest\n";
+ ini = new Wini(new StringReader(context));
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(1, ini.size());
+ Assert.assertEquals(1, iniMap.size());
+ Assert.assertEquals(1, ini.get("?").size());
+ Assert.assertEquals(1, iniMap.get("?").size());
+
+ context = "[profile1]\n"
+ + "key \n";
+ ini = new Wini(new StringReader(context));
+ iniMap = ProfileUtils.parseFile(new StringReader(context));
+ Assert.assertEquals(1, ini.size());
+ Assert.assertEquals(1, iniMap.size());
+ Assert.assertNull(ini.get("profile1").get("key"));
+ Assert.assertNull(iniMap.get("profile1").get("key"));
+
+ context = "[profile1]\n"
+ + " = value\n";
+ try {
+ new Wini(new StringReader(context));
+ Assert.fail();
+ } catch (Exception e) {
+ Assert.assertTrue(e.getMessage().contains("parse error"));
+ }
+ try {
+ ProfileUtils.parseFile(new StringReader(context));
+ Assert.fail();
+ } catch (Exception e) {
+ Assert.assertEquals("Property did not have a name on line 2", e.getMessage());
+ }
+ }
+
+}
diff --git a/aliyun-java-sdk-core/src/test/resources/configTest.ini b/aliyun-java-sdk-core/src/test/resources/configTest.ini
index de3379e6cd..cfcbacbc96 100644
--- a/aliyun-java-sdk-core/src/test/resources/configTest.ini
+++ b/aliyun-java-sdk-core/src/test/resources/configTest.ini
@@ -1,4 +1,18 @@
-[default]
+[ ] ;invalid
+enable = false ;comments
+enable = true #comments
+type = access_key
+access_key_id = foo
+
+[client] ;comments
+enable = false ;comments
+enable = true #comments
+type = access_key # type
+access_key_id = foo # access_key_id
+access_key_secret = bar # access_key_secret
+
+
+[default]
enable = true
type = access_key
access_key_id = foo