Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,17 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import org.cyclonedx.model.*;
import org.cyclonedx.model.OrganizationalContact;
import org.cyclonedx.model.OrganizationalEntity;
import org.cyclonedx.model.Property;
import org.cyclonedx.model.Tool;
import org.cyclonedx.model.VersionFilter;
import org.cyclonedx.model.metadata.ToolInformation;
import org.cyclonedx.util.deserializer.VulnerabilityDeserializer;
import org.cyclonedx.util.serializer.CustomDateSerializer;

/**
Expand Down Expand Up @@ -59,6 +66,7 @@
"properties"
})
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonDeserialize(using = VulnerabilityDeserializer.class)
public class Vulnerability
{
public Vulnerability() {}
Expand Down Expand Up @@ -90,7 +98,11 @@ public Vulnerability() {}
private Credits credits;
@JacksonXmlElementWrapper(localName = "tools")
@JacksonXmlProperty(localName = "tool")
@Deprecated
private List<Tool> tools;
@JacksonXmlProperty(localName = "tools")
@VersionFilter(org.cyclonedx.Version.VERSION_15)
private ToolInformation toolInformation;
private Analysis analysis;
private List<Affect> affects;
private List<Property> properties;
Expand Down Expand Up @@ -245,6 +257,17 @@ public void setTools(final List<Tool> tools) {
this.tools = tools;
}

@JacksonXmlElementWrapper(localName = "tools")
@JacksonXmlProperty(localName = "tool")
@JsonProperty("tools")
public ToolInformation getToolChoice() {
return toolInformation;
}

public void setToolChoice(final ToolInformation toolInformation) {
this.toolInformation = toolInformation;
}

public Analysis getAnalysis() {
return analysis;
}
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/org/cyclonedx/util/TimestampUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* This file is part of CycloneDX Core (Java).
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.cyclonedx.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

@SuppressWarnings("unused")
public final class TimestampUtils {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");

private TimestampUtils() {}

public static Date parseTimestamp(String text) {
try {
return DATE_FORMAT.parse(text);
} catch (ParseException | NullPointerException e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* This file is part of CycloneDX Core (Java).
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.cyclonedx.util.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.cyclonedx.model.vulnerability.Vulnerability;

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

public class AffectDeserializer
extends JsonDeserializer<Vulnerability.Affect>
{
private final ObjectMapper mapper = new ObjectMapper();

@Override
public Vulnerability.Affect deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);

Vulnerability.Affect affect = new Vulnerability.Affect();

JsonNode refNode = node.get("ref");
if (refNode != null) {
affect.setRef(refNode.asText());
}

JsonNode versionsNode = node.get("versions");
if (versionsNode != null) {
if (versionsNode.isArray()) {
List<Vulnerability.Version> versions = mapper.convertValue(node.get("versions"), new TypeReference<List<Vulnerability.Version>>() {});
affect.setVersions(versions);
} else if (versionsNode.has("version")) {
JsonNode versionNode = versionsNode.get("version");
if (versionNode.isArray()) {
List<Vulnerability.Version> versions = mapper.convertValue(versionNode, new TypeReference<List<Vulnerability.Version>>() {});
affect.setVersions(versions);
} else {
affect.setVersions(Collections.singletonList(
mapper.convertValue(versionNode, Vulnerability.Version.class)
));
}
}
}

return affect;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* This file is part of CycloneDX Core (Java).
*
* Licensed 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.cyclonedx.util.deserializer;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.cyclonedx.model.vulnerability.Vulnerability;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class AffectsDeserializer
extends JsonDeserializer<List<Vulnerability.Affect>>
{
private final AffectDeserializer affectDeserializer = new AffectDeserializer();
private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public List<Vulnerability.Affect> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
return parseAffects(node.has("target") ? node.get("target") : node, p, ctxt);
}

private List<Vulnerability.Affect> parseAffects(JsonNode node, JsonParser p, DeserializationContext ctxt) throws IOException {
List<Vulnerability.Affect> affects = new ArrayList<>();
ArrayNode nodes = DeserializerUtils.getArrayNode(node, objectMapper);

for (JsonNode affectNode : nodes) {
affects.add(parseAffect(affectNode, p, ctxt));
}

return affects;
}

private Vulnerability.Affect parseAffect(JsonNode node, JsonParser p, DeserializationContext ctxt) throws IOException {
JsonParser affectParser = node.traverse(p.getCodec());
affectParser.nextToken();
return affectDeserializer.deserialize(affectParser, ctxt);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package org.cyclonedx.util.deserializer;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
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.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
Expand All @@ -22,17 +16,18 @@
import org.cyclonedx.model.OrganizationalContact;
import org.cyclonedx.model.OrganizationalEntity;
import org.cyclonedx.model.Property;
import org.cyclonedx.model.Service;
import org.cyclonedx.model.Tool;
import org.cyclonedx.model.metadata.ToolInformation;
import org.cyclonedx.util.TimestampUtils;

public class MetadataDeserializer
extends JsonDeserializer<Metadata> {

private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
private final LifecycleDeserializer lifecycleDeserializer = new LifecycleDeserializer();
private final PropertiesDeserializer propertiesDeserializer = new PropertiesDeserializer();
private final LicenseDeserializer licenseDeserializer = new LicenseDeserializer();
private final ToolsDeserializer toolsDeserializer = new ToolsDeserializer();
private final ToolInformationDeserializer toolInformationDeserializer = new ToolInformationDeserializer();

@Override
public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
Expand Down Expand Up @@ -95,34 +90,19 @@ public Metadata deserialize(JsonParser jsonParser, DeserializationContext ctxt)
}

if (node.has("tools")) {
parseTools(node.get("tools"), metadata, mapper);
}

return metadata;
}

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>>() {});
toolInformation.setComponents(components);
} else if (componentsNode.isObject()) {
Component component = mapper.convertValue(componentsNode, Component.class);
toolInformation.setComponents(Collections.singletonList(component));
JsonNode toolsNode = node.get("tools");
JsonParser toolsParser = toolsNode.traverse(jsonParser.getCodec());
toolsParser.nextToken();
if (toolsNode.has("components") || toolsNode.has("services")) {
ToolInformation toolInformation = toolInformationDeserializer.deserialize(toolsParser, ctxt);
metadata.setToolChoice(toolInformation);
} else {
List<Tool> tools = toolsDeserializer.deserialize(toolsParser, ctxt);
metadata.setTools(tools);
}
}
}

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>>() {});
toolInformation.setServices(services);
} else if (servicesNode.isObject()) {
Service service = mapper.convertValue(servicesNode, Service.class);
toolInformation.setServices(Collections.singletonList(service));
}
}
return metadata;
}

static List<OrganizationalContact> deserializeOrganizationalContact(JsonNode node, final ObjectMapper mapper) {
Expand All @@ -143,35 +123,6 @@ else if (node.isObject()) {
return organizationalContactList;
}

private void parseTools(JsonNode toolsNode, Metadata metadata, ObjectMapper mapper) throws JsonProcessingException {
if (toolsNode.isArray()) {
setToolInfo(toolsNode, metadata, mapper);
} else if (toolsNode.has("tool")) {
JsonNode toolNode = toolsNode.get("tool");
if (toolNode.isArray()) {
setToolInfo(toolNode, metadata, mapper);
} else {
Tool tool = mapper.convertValue(toolNode, Tool.class);
metadata.setTools(Collections.singletonList(tool));
}
} else {
ToolInformation toolInformation = new ToolInformation();
if (toolsNode.has("components")) {
parseComponents(toolsNode.get("components"), toolInformation, mapper);
}
if (toolsNode.has("services")) {
parseServices(toolsNode.get("services"), toolInformation, mapper);
}
metadata.setToolChoice(toolInformation);
}
}

private void setToolInfo(JsonNode node, Metadata metadata, ObjectMapper mapper){
List<Tool> tools = mapper.convertValue(node, new TypeReference<List<Tool>>() {});
metadata.setTools(tools);
}


static void deserializeAuthor(
JsonNode node,
final ObjectMapper mapper,
Expand All @@ -192,12 +143,7 @@ private ObjectMapper getMapper(JsonParser jsonParser) {
private void setTimestamp(JsonNode node, Metadata metadata) {
JsonNode timestampNode = node.get("timestamp");
if (timestampNode != null && timestampNode.isTextual()) {
try {
Date timestamp = dateFormat.parse(timestampNode.textValue());
metadata.setTimestamp(timestamp);
} catch (ParseException e) {
// Handle parsing exception
}
metadata.setTimestamp(TimestampUtils.parseTimestamp(timestampNode.textValue()));
}
}
}
Loading