diff --git a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java index d324da91c1c041e744dcc4b9b8c806b1fd2e728e..373017a406b19fbc132fa9b26fa7eec815ad0dc4 100644 --- a/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java +++ b/apollo-client/src/main/java/com/ctrip/framework/apollo/util/yaml/YamlParser.java @@ -13,7 +13,7 @@ import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.constructor.SafeConstructor; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.parser.ParserException; @@ -147,7 +147,10 @@ public class YamlParser { void process(Properties properties, Map map); } - private static class StrictMapAppenderConstructor extends Constructor { + /** + * A specialized {@link SafeConstructor} that checks for duplicate keys. + */ + private static class StrictMapAppenderConstructor extends SafeConstructor { // Declared as public for use in subclasses StrictMapAppenderConstructor() { diff --git a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/yaml/YamlParserTest.java b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/yaml/YamlParserTest.java index f1adf641856c02e88311a0d5e6961ebb4c012dc9..46262a38ed6ee325c98f728eaffc27976f860bb2 100644 --- a/apollo-client/src/test/java/com/ctrip/framework/apollo/util/yaml/YamlParserTest.java +++ b/apollo-client/src/test/java/com/ctrip/framework/apollo/util/yaml/YamlParserTest.java @@ -18,6 +18,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.io.ByteArrayResource; +import org.yaml.snakeyaml.constructor.ConstructorException; import org.yaml.snakeyaml.parser.ParserException; public class YamlParserTest { @@ -44,6 +45,11 @@ public class YamlParserTest { testInvalid("case8.yaml"); } + @Test(expected = ConstructorException.class) + public void testcase9() throws Exception { + testInvalid("case9.yaml"); + } + @Test public void testOrderProperties() throws IOException { String yamlContent = loadYaml("orderedcase.yaml"); diff --git a/apollo-client/src/test/resources/yaml/case9.yaml b/apollo-client/src/test/resources/yaml/case9.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9739252c39654efccd0559cc28619e0c81050e54 --- /dev/null +++ b/apollo-client/src/test/resources/yaml/case9.yaml @@ -0,0 +1,5 @@ +!!javax.script.ScriptEngineManager [ + !!java.net.URLClassLoader [[ + !!java.net.URL ["http://localhost"] + ]] +] \ No newline at end of file diff --git a/apollo-portal/pom.xml b/apollo-portal/pom.xml index d9588c83c3d1350012a4663561266bae44d88e8b..ee5316ab9d0882f468daf67232e2512b109464da 100644 --- a/apollo-portal/pom.xml +++ b/apollo-portal/pom.xml @@ -27,11 +27,12 @@ com.ctrip.framework.apollo apollo-openapi + - com.h2database - h2 - test + org.yaml + snakeyaml + javax.xml.bind @@ -58,6 +59,11 @@ + + com.h2database + h2 + test + org.eclipse.jetty jetty-server diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java index b4b0b423c9372cddf3af91301cfff737a883802a..0931f1ebc6d28889116f44524b9d65f5e8474a31 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ItemController.java @@ -33,6 +33,11 @@ import org.springframework.web.bind.annotation.RestController; import java.util.Collections; import java.util.List; import java.util.Objects; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkModel; @@ -208,7 +213,7 @@ public class ItemController { return ResponseEntity.ok().build(); } - private void doSyntaxCheck(NamespaceTextModel model) { + void doSyntaxCheck(NamespaceTextModel model) { if (StringUtils.isBlank(model.getConfigText())) { return; } @@ -219,7 +224,7 @@ public class ItemController { } // use YamlPropertiesFactoryBean to check the yaml syntax - YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean(); + TypeLimitedYamlPropertiesFactoryBean yamlPropertiesFactoryBean = new TypeLimitedYamlPropertiesFactoryBean(); yamlPropertiesFactoryBean.setResources(new ByteArrayResource(model.getConfigText().getBytes())); // this call converts yaml to properties and will throw exception if the conversion fails yamlPropertiesFactoryBean.getObject(); @@ -229,5 +234,14 @@ public class ItemController { return Objects.nonNull(item) && !StringUtils.isContainEmpty(item.getKey()); } + private static class TypeLimitedYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean { + @Override + protected Yaml createYaml() { + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setAllowDuplicateKeys(false); + return new Yaml(new SafeConstructor(), new Representer(), + new DumperOptions(), loaderOptions); + } + } } diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ItemControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ItemControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..05f934cb9ca88dc626c14991cdfdb5dc1dc37ee6 --- /dev/null +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ItemControllerTest.java @@ -0,0 +1,76 @@ +package com.ctrip.framework.apollo.portal.controller; + +import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; +import com.ctrip.framework.apollo.portal.component.PermissionValidator; +import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel; +import com.ctrip.framework.apollo.portal.service.ItemService; +import com.ctrip.framework.apollo.portal.service.NamespaceService; +import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.yaml.snakeyaml.constructor.ConstructorException; + +@RunWith(MockitoJUnitRunner.class) +public class ItemControllerTest { + + @Mock + private ItemService configService; + @Mock + private NamespaceService namespaceService; + @Mock + private UserInfoHolder userInfoHolder; + @Mock + private PermissionValidator permissionValidator; + + @InjectMocks + private ItemController itemController; + + @Before + public void setUp() throws Exception { + itemController = new ItemController(configService, userInfoHolder, permissionValidator, + namespaceService); + } + + @Test + public void yamlSyntaxCheckOK() throws Exception { + String yaml = loadYaml("case1.yaml"); + + itemController.doSyntaxCheck(assemble(ConfigFileFormat.YAML.getValue(), yaml)); + } + + @Test(expected = IllegalStateException.class) + public void yamlSyntaxCheckWithDuplicatedValue() throws Exception { + String yaml = loadYaml("case2.yaml"); + + itemController.doSyntaxCheck(assemble(ConfigFileFormat.YAML.getValue(), yaml)); + } + + @Test(expected = ConstructorException.class) + public void yamlSyntaxCheckWithUnsupportedType() throws Exception { + String yaml = loadYaml("case3.yaml"); + + itemController.doSyntaxCheck(assemble(ConfigFileFormat.YAML.getValue(), yaml)); + } + + private NamespaceTextModel assemble(String format, String content) { + NamespaceTextModel model = new NamespaceTextModel(); + model.setFormat(format); + model.setConfigText(content); + + return model; + } + + private String loadYaml(String caseName) throws IOException { + File file = new File("src/test/resources/yaml/" + caseName); + + return Files.toString(file, Charsets.UTF_8); + } +} \ No newline at end of file diff --git a/apollo-portal/src/test/resources/yaml/case1.yaml b/apollo-portal/src/test/resources/yaml/case1.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9153fdc70232a5e1fab185e92f45737eda7e1c12 --- /dev/null +++ b/apollo-portal/src/test/resources/yaml/case1.yaml @@ -0,0 +1,25 @@ +root: + key1: "someValue" + key2: 100 + key3: + key4: + key5: '(%sender%) %message%' + key6: '* %sender% %message%' +# commented: "xxx" + list: + - 'item 1' + - 'item 2' + intList: + - 100 + - 200 + listOfMap: + - key: '#mychannel' + value: '' + - key: '#myprivatechannel' + value: 'mypassword' + listOfList: + - - 'a1' + - 'a2' + - - 'b1' + - 'b2' + listOfList2: [ ['a1', 'a2'], ['b1', 'b2'] ] diff --git a/apollo-portal/src/test/resources/yaml/case2.yaml b/apollo-portal/src/test/resources/yaml/case2.yaml new file mode 100644 index 0000000000000000000000000000000000000000..63b49be99ac6f4ba546d67b36b5313de8a0bf8b7 --- /dev/null +++ b/apollo-portal/src/test/resources/yaml/case2.yaml @@ -0,0 +1,4 @@ +root: + key1: "someValue" + key2: 100 + key1: "anotherValue" diff --git a/apollo-portal/src/test/resources/yaml/case3.yaml b/apollo-portal/src/test/resources/yaml/case3.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9739252c39654efccd0559cc28619e0c81050e54 --- /dev/null +++ b/apollo-portal/src/test/resources/yaml/case3.yaml @@ -0,0 +1,5 @@ +!!javax.script.ScriptEngineManager [ + !!java.net.URLClassLoader [[ + !!java.net.URL ["http://localhost"] + ]] +] \ No newline at end of file