Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parseexception with ancestors fix #365

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
import org.cyclonedx.model.Ancestors;
import org.cyclonedx.model.Component;
Expand Down Expand Up @@ -72,10 +77,25 @@ public ComponentWrapper deserialize(
return null;
}

Component[] components = parser.readValueAs(Component[].class);

wrapper.setComponents(Arrays.asList(components));

List<Component> components = Collections.emptyList();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can determine, the issue arises due to MetadataDeserializer using the shortcut of reading the tree from the XML (this is only an XML issue) token stream, then using a unconfigured ObjectMapper to convert to the target types. The context is then lost (e.g. XML parser, XML deserialization context) within child deserializers and we enter this deserializer re-parsed then tree now as plain JSON tokens with a vanilla JSON parser.

This list of components (when nested in metadata/../ancestors etc) might be absent (no tokens), a single object, or an array of objects. Fix here is to explicitly handle these cases.

Not 100% happy - still a hack on top of the existing hack to handle component object lists. 🤷

JsonToken currentToken = parser.currentToken();
if (currentToken == JsonToken.START_ARRAY) {
components = Arrays.asList(parser.readValueAs(Component[].class));
} else if (currentToken == JsonToken.START_OBJECT) {
// This is possible for XML input when tree has been read, then parsed with token buffer parser
ObjectNode node = parser.readValueAs(ObjectNode.class);
if (node.has("component")) {
JsonNode component = node.get("component");
JsonParser componentsParser = component.traverse(parser.getCodec());
if (component.isArray()) {
components = Arrays.asList(componentsParser.readValueAs(Component[].class));
} else {
components = Collections.singletonList(componentsParser.readValueAs(Component.class));
}
}
}
wrapper.setComponents(components);
return wrapper;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
Expand All @@ -28,8 +28,6 @@
public class MetadataDeserializer
extends JsonDeserializer<Metadata> {

private final ObjectMapper mapper = new ObjectMapper();

private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");

private final LifecycleDeserializer lifecycleDeserializer = new LifecycleDeserializer();
Expand All @@ -42,6 +40,13 @@ public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt)

Metadata metadata = new Metadata();

ObjectMapper mapper;
if (jsonParser.getCodec() instanceof ObjectMapper) {
mapper = (ObjectMapper) jsonParser.getCodec();
} else {
mapper = new ObjectMapper();
}

// Parsing other fields in the Metadata object
if (node.has("authors")) {
JsonNode authorsNode = node.get("authors");
Expand Down Expand Up @@ -127,10 +132,10 @@ else if (toolsNode.has("tool")) {
else {
ToolInformation toolInformation = new ToolInformation();
if (toolsNode.has("components")) {
parseComponents(toolsNode.get("components"), toolInformation);
parseComponents(toolsNode.get("components"), toolInformation, mapper);
}
if (toolsNode.has("services")) {
parseServices(toolsNode.get("services"), toolInformation);
parseServices(toolsNode.get("services"), toolInformation, mapper);
}
metadata.setToolChoice(toolInformation);
}
Expand All @@ -139,7 +144,7 @@ else if (toolsNode.has("tool")) {
return metadata;
}

private void parseComponents(JsonNode componentsNode, ToolInformation toolInformation) {
private void parseComponents(JsonNode componentsNode, ToolInformation toolInformation, ObjectMapper mapper) {
if (componentsNode != null) {
if (componentsNode.isArray()) {
List<Component> components = mapper.convertValue(componentsNode, new TypeReference<List<Component>>() {});
Expand All @@ -151,7 +156,7 @@ private void parseComponents(JsonNode componentsNode, ToolInformation toolInform
}
}

private void parseServices(JsonNode servicesNode, ToolInformation toolInformation) {
private void parseServices(JsonNode servicesNode, ToolInformation toolInformation, ObjectMapper mapper) {
if (servicesNode != null) {
if (servicesNode.isArray()) {
List<Service> services = mapper.convertValue(servicesNode, new TypeReference<List<Service>>() {});
Expand Down
19 changes: 18 additions & 1 deletion src/test/java/org/cyclonedx/parsers/XmlParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.cyclonedx.parsers;

import org.cyclonedx.CycloneDxSchema;
import org.cyclonedx.CycloneDxSchema.Version;
import org.cyclonedx.Version;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
Expand Down Expand Up @@ -74,6 +76,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;

public class XmlParserTest
extends AbstractParserTest
Expand Down Expand Up @@ -165,6 +168,20 @@ private void testPedigreeFromExample(final Pedigree pedigree) {
assertEquals(2, pedigree.getPatches().get(1).getResolves().size());
}

@Test
public void testValid12BomWithMetadataPedigree() throws Exception {
final File file = new File(Objects.requireNonNull(this.getClass().getResource("/bom-1.2-metadata-pedigree.xml")).getFile());
final XmlParser parser = new XmlParser();
final boolean valid = parser.isValid(file, CycloneDxSchema.Version.VERSION_12);
assertTrue(valid);

final Bom bom = parser.parse(file);
Pedigree pedigree = bom.getMetadata().getComponent().getPedigree();
assertEquals(2, pedigree.getAncestors().getComponents().size());
assertEquals(1, pedigree.getDescendants().getComponents().size());
assertEquals(0, pedigree.getVariants().getComponents().size());
}

@Test
public void testParsedObjects10Bom() throws Exception {
final Bom bom = getXmlBom("bom-1.0.xml");
Expand Down Expand Up @@ -388,7 +405,7 @@ public void testIssue336Regression() throws Exception {
assertEquals("foo", bom.getMetadata().getComponent().getProperties().get(0).getName());
assertEquals("bar", bom.getMetadata().getComponent().getProperties().get(0).getValue());
}

@Test
public void testIssue338RegressionWithSingleTool() throws Exception {
final Bom bom = getXmlBom("regression/issue338-single-tool.xml");
Expand Down
35 changes: 35 additions & 0 deletions src/test/resources/bom-1.2-metadata-pedigree.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.2"
serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79"
version="1">
<metadata>
<component type="library">
<group>com.acme</group>
<name>sample-library</name>
<version>1.0.0</version>
<pedigree>
<ancestors>
<component type="library">
<group>org.example</group>
<name>sample-library-ancestor-1</name>
<version>1.0.0</version>
</component>
<component type="library">
<group>org.example</group>
<name>sample-library-ancestor-2</name>
<version>1.0.0</version>
</component>
</ancestors>
<descendants>
<component type="library">
<group>org.example</group>
<name>sample-library-descendant</name>
<version>1.0.1</version>
</component>
</descendants>
<variants>
</variants>
</pedigree>
</component>
</metadata>
</bom>
Loading