Skip to content

Commit

Permalink
Merge pull request #235 from synaos-bwi/optional_encoding
Browse files Browse the repository at this point in the history
Optional encoding of license text
  • Loading branch information
stevespringett committed Nov 20, 2022
2 parents f595c43 + 02b4a28 commit d94610d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/main/java/org/cyclonedx/CycloneDxSchema.java
Expand Up @@ -82,6 +82,7 @@ public double getVersion() {
/**
* Returns the CycloneDX JsonSchema for the specified schema version.
* @param schemaVersion The version to return the schema for
* @param mapper is to provide a Jackson ObjectMapper
* @return a Schema
* @throws IOException when errors are encountered
* @since 6.0.0
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/cyclonedx/model/AttachmentText.java
Expand Up @@ -19,15 +19,19 @@
package org.cyclonedx.model;

import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText;

@SuppressWarnings("unused")
@JsonPropertyOrder({
"content-type",
"encoding"})
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AttachmentText {

@JacksonXmlProperty(isAttribute = true)
Expand Down
106 changes: 89 additions & 17 deletions src/main/java/org/cyclonedx/util/LicenseResolver.java
Expand Up @@ -25,6 +25,7 @@
import org.cyclonedx.model.AttachmentText;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
Expand All @@ -44,7 +45,7 @@ private LicenseResolver() {
* @return a LicenseChoice object if resolution was successful, or null if unresolved
*/
public static LicenseChoice resolve(final String licenseString) {
return resolve(licenseString, true);
return resolve(licenseString, new LicenseTextSettings(true, LicenseEncoding.BASE64));
}

/**
Expand All @@ -55,17 +56,32 @@ public static LicenseChoice resolve(final String licenseString) {
* @return a LicenseChoice object if resolution was successful, or null if unresolved
*/
public static LicenseChoice resolve(final String licenseString, final boolean includeLicenseText) {
return resolve(licenseString, new LicenseTextSettings( includeLicenseText, LicenseEncoding.BASE64));
}

static LicenseChoice resolve(final String licenseString, final boolean includeLicenseText, final ObjectMapper mapper) {
return resolve(licenseString, new LicenseTextSettings( includeLicenseText, LicenseEncoding.BASE64), mapper);
}

/**
* Attempts to resolve the specified license string via SPDX license identifier and expression
* parsing first. If SPDX resolution is not successful, the method will attempt fuzzy matching.
* @param licenseString the license string to resolve
* @param licenseTextSettings specifies settings regarding the entire text of the resolved license
* @return a LicenseChoice object if resolution was successful, or null if unresolved
*/
public static LicenseChoice resolve(final String licenseString, final LicenseTextSettings licenseTextSettings) {
final ObjectMapper mapper = new ObjectMapper();

return resolve(licenseString, includeLicenseText, mapper);
return resolve(licenseString, licenseTextSettings, mapper);
}

static LicenseChoice resolve(final String licenseString, final boolean includeLicenseText, final ObjectMapper mapper) {
static LicenseChoice resolve(final String licenseString, final LicenseTextSettings licenseTextSettings, final ObjectMapper mapper) {
try {
LicenseChoice licenseChoice = resolveLicenseString(licenseString, includeLicenseText, mapper);
LicenseChoice licenseChoice = resolveLicenseString(licenseString, licenseTextSettings, mapper);

if (licenseChoice == null) {
licenseChoice = resolveFuzzyMatching(licenseString, includeLicenseText, mapper);
licenseChoice = resolveFuzzyMatching(licenseString, licenseTextSettings, mapper);
}
return licenseChoice;
} catch (IOException ex) {
Expand All @@ -77,12 +93,12 @@ static LicenseChoice resolve(final String licenseString, final boolean includeLi
* Given an SPDX license ID or expression, this method will resolve the license(s) and
* return a LicenseChoice object.
* @param licenseString the license string to resolve
* @param includeLicenseText specifies is the resolved license will include the entire text of the license
* @param licenseTextSettings specifies settings regarding the entire text of the resolved license
* @param mapper is to provide a Jackson ObjectMapper
* @return a LicenseChoice object if resolved, or null
* @throws IOException an exception while parsing the license string
*/
private static LicenseChoice resolveLicenseString(String licenseString, boolean includeLicenseText, final ObjectMapper mapper)
private static LicenseChoice resolveLicenseString(String licenseString, LicenseTextSettings licenseTextSettings, final ObjectMapper mapper)
throws IOException
{
final InputStream is = LicenseResolver.class.getResourceAsStream("/licenses/licenses.json");
Expand All @@ -95,9 +111,9 @@ private static LicenseChoice resolveLicenseString(String licenseString, boolean
final String primaryLicenseUrl = (licenseDetail.seeAlso != null && !licenseDetail.seeAlso.isEmpty()) ? licenseDetail.seeAlso.get(0) : null;

if (licenseString.trim().equalsIgnoreCase(licenseDetail.licenseId)) {
return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, licenseTextSettings);
} else if (licenseString.trim().equalsIgnoreCase(licenseDetail.name)) {
return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
return createLicenseChoice(licenseDetail.licenseId, primaryLicenseUrl, licenseDetail.isDeprecatedLicenseId, licenseTextSettings);
} else {

if (licenseDetail.isDeprecatedLicenseId) {
Expand All @@ -110,7 +126,7 @@ private static LicenseChoice resolveLicenseString(String licenseString, boolean
final String licenseStringModified = urlNormalize(licenseString);

if (licenseStringModified.equalsIgnoreCase(urlNormalize(url))) {
return createLicenseChoice(licenseDetail.licenseId, url, licenseDetail.isDeprecatedLicenseId, includeLicenseText);
return createLicenseChoice(licenseDetail.licenseId, url, licenseDetail.isDeprecatedLicenseId, licenseTextSettings);
}
}
}
Expand All @@ -125,11 +141,11 @@ private static LicenseChoice resolveLicenseString(String licenseString, boolean
/**
* Attempts to perform high-confidence license resolution with unstructured text as input.
* @param licenseString the license string (not the actual license text)
* @param includeLicenseText specifies is the resolved license will include the entire text of the license
* @param licenseTextSettings specifies settings regarding the entire text of the resolved license
* @param mapper is to provide a Jackson ObjectMapper
* @return a LicenseChoice object if resolved, otherwise null
*/
private static LicenseChoice resolveFuzzyMatching(final String licenseString, final boolean includeLicenseText, final ObjectMapper mapper) throws IOException {
private static LicenseChoice resolveFuzzyMatching(final String licenseString, final LicenseTextSettings licenseTextSettings, final ObjectMapper mapper) throws IOException {
if (licenseString == null) {
return null;
}
Expand All @@ -148,7 +164,7 @@ private static LicenseChoice resolveFuzzyMatching(final String licenseString, fi
lc.setExpression(licenseMapping.exp);
return lc;
} else {
return createLicenseChoice(licenseMapping.exp, null, false, includeLicenseText);
return createLicenseChoice(licenseMapping.exp, null, false, licenseTextSettings);
}
}
}
Expand All @@ -167,26 +183,82 @@ private static String urlNormalize(String input) {
.replace("http://", "");
}

private static LicenseChoice createLicenseChoice(String licenseId, String primaryLicenseUrl, boolean isDeprecatedLicenseId, boolean includeLicenseText) throws IOException {
private static LicenseChoice createLicenseChoice(String licenseId, String primaryLicenseUrl, boolean isDeprecatedLicenseId, LicenseTextSettings licenseTextSettings ) throws IOException {
final LicenseChoice choice = new LicenseChoice();
final License license = new License();
license.setId(licenseId);
license.setUrl(primaryLicenseUrl);
if (!isDeprecatedLicenseId && includeLicenseText) {
if (!isDeprecatedLicenseId && licenseTextSettings.isTextIncluded()) {
final InputStream is = LicenseResolver.class.getResourceAsStream("/licenses/" + licenseId + ".txt");
if (is != null) {
final String text = IOUtils.toString(is, StandardCharsets.UTF_8);
final AttachmentText attachment = new AttachmentText();
attachment.setContentType("plain/text");
attachment.setEncoding("base64");
attachment.setText(Base64.getEncoder().encodeToString(text.getBytes()));
switch(licenseTextSettings.getEncoding()){
case NONE:
attachment.setEncoding(null);
attachment.setText(text);
break;
case BASE64:
attachment.setEncoding(licenseTextSettings.getEncoding().toString());
attachment.setText(Base64.getEncoder().encodeToString(text.getBytes(Charset.defaultCharset())));
break;
default:
throw new IllegalArgumentException("Unhandled License Encoding:" + licenseTextSettings.getEncoding().toString() );
}
license.setLicenseText(attachment);
}
}
choice.addLicense(license);
return choice;
}

/**
* Lists possible choices for license text encoding
*/
public enum LicenseEncoding{
BASE64("base64"),
NONE("none");

private final String encodingName;

/**
* Constructor with a string representation of the enum value
* @param encodingName The string representation of the enum value
*/
LicenseEncoding(String encodingName) {
this.encodingName = encodingName;
}
public String toString() {
return encodingName;
}
}

/**
* Data class aggregating settings for license text output
*/
public static class LicenseTextSettings {
public boolean isTextIncluded;
public LicenseEncoding encoding;

public LicenseTextSettings(boolean includeLicenseText, LicenseEncoding encoding) {
this.isTextIncluded = includeLicenseText;
this.encoding = encoding;
}
public boolean isTextIncluded() {
return isTextIncluded;
}
public void setTextIncluded(boolean include) {
this.isTextIncluded = include;
}
public LicenseEncoding getEncoding() {
return encoding;
}
public void setEncoding(LicenseEncoding encoding) {
this.encoding = encoding;
}
}

private static class LicenseDetail {
public String reference;
public boolean isDeprecatedLicenseId;
Expand Down
19 changes: 19 additions & 0 deletions src/test/java/org/cyclonedx/util/LicenseResolverTest.java
Expand Up @@ -23,6 +23,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

public class LicenseResolverTest {

Expand All @@ -49,6 +50,24 @@ public void resolveTestSingleLicense() {
assertNotNull(c1.getLicenses().get(0).getAttachmentText().getText());
assertEquals("plain/text", c1.getLicenses().get(0).getAttachmentText().getContentType());
assertEquals("base64", c1.getLicenses().get(0).getAttachmentText().getEncoding());

LicenseResolver.LicenseTextSettings textSettings = new LicenseResolver.LicenseTextSettings( true, LicenseResolver.LicenseEncoding.NONE);
LicenseChoice c2 = LicenseResolver.resolve("GPL-3.0-only", textSettings);
assertEquals(1, c2.getLicenses().size());
assertEquals("GPL-3.0-only", c2.getLicenses().get(0).getId());
assertEquals("https://www.gnu.org/licenses/gpl-3.0-standalone.html", c2.getLicenses().get(0).getUrl());
assertNotNull(c2.getLicenses().get(0).getAttachmentText().getText());
assertEquals("plain/text", c2.getLicenses().get(0).getAttachmentText().getContentType());
assertNull(c2.getLicenses().get(0).getAttachmentText().getEncoding());

textSettings = new LicenseResolver.LicenseTextSettings( true, LicenseResolver.LicenseEncoding.BASE64);
LicenseChoice c3 = LicenseResolver.resolve("GPL-3.0-only", textSettings);
assertEquals(1, c3.getLicenses().size());
assertEquals("GPL-3.0-only", c3.getLicenses().get(0).getId());
assertEquals("https://www.gnu.org/licenses/gpl-3.0-standalone.html", c3.getLicenses().get(0).getUrl());
assertNotNull(c3.getLicenses().get(0).getAttachmentText().getText());
assertEquals("plain/text", c3.getLicenses().get(0).getAttachmentText().getContentType());
assertEquals("base64", c3.getLicenses().get(0).getAttachmentText().getEncoding());
}

@Test
Expand Down

0 comments on commit d94610d

Please sign in to comment.