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

Update snappy-java and migrate mjson to org.json to address CVEs #1670

Merged
merged 6 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 2 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,10 @@ jacocoTestReport {

dependencies {
implementation "commons-logging:commons-logging:1.1.1"
implementation "org.xerial.snappy:snappy-java:1.1.8.4"
implementation "org.xerial.snappy:snappy-java:1.1.10.1"
implementation "org.apache.commons:commons-compress:1.22"
implementation 'org.tukaani:xz:1.8'
implementation ('org.sharegov:mjson:1.4.1') {
exclude group: "junit"
}
implementation "org.json:json:20230227"
bbimber marked this conversation as resolved.
Show resolved Hide resolved

implementation 'org.openjdk.nashorn:nashorn-core:15.4'

Expand Down
111 changes: 29 additions & 82 deletions src/main/java/htsjdk/beta/io/bundle/BundleJSON.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import htsjdk.io.IOPath;
import htsjdk.samtools.util.Log;
import htsjdk.utils.ValidationUtils;
import mjson.Json;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -50,10 +51,10 @@ public class BundleJSON {
* @throws IllegalArgumentException if any resource in bundle is not an IOPathResources.
*/
public static String toJSON(final Bundle bundle) {
final Json outerJSON = Json.object()
.set(JSON_PROPERTY_SCHEMA_NAME, JSON_SCHEMA_NAME)
.set(JSON_PROPERTY_SCHEMA_VERSION, JSON_SCHEMA_VERSION)
.set(JSON_PROPERTY_PRIMARY, bundle.getPrimaryContentType());
final JSONObject outerJSON = new JSONObject()
.put(JSON_PROPERTY_SCHEMA_NAME, JSON_SCHEMA_NAME)
.put(JSON_PROPERTY_SCHEMA_VERSION, JSON_SCHEMA_VERSION)
.put(JSON_PROPERTY_PRIMARY, bundle.getPrimaryContentType());

bundle.forEach(bundleResource -> {
final Optional<IOPath> resourcePath = bundleResource.getIOPath();
Expand All @@ -62,14 +63,14 @@ public static String toJSON(final Bundle bundle) {
}

// generate JSON for each bundle resource
final Json resourceJSON = Json.object().set(JSON_PROPERTY_PATH, resourcePath.get().getURIString());
final JSONObject resourceJSON = new JSONObject().put(JSON_PROPERTY_PATH, resourcePath.get().getURIString());
if (bundleResource.getFileFormat().isPresent()) {
resourceJSON.set(JSON_PROPERTY_FORMAT, bundleResource.getFileFormat().get());
resourceJSON.put(JSON_PROPERTY_FORMAT, bundleResource.getFileFormat().get());
}
outerJSON.set(bundleResource.getContentType(), resourceJSON);
outerJSON.put(bundleResource.getContentType(), resourceJSON);
});

return prettyPrintJSON(outerJSON);
return outerJSON.toString(1);
}

/**
Expand Down Expand Up @@ -100,116 +101,62 @@ public static <T extends IOPath> Bundle toBundle(
String primaryContentType;

try {
final Json jsonDocument = Json.read(jsonString);
if (jsonDocument == null || jsonString.length() < 1) {
final JSONObject jsonDocument = new JSONObject(jsonString);
if (jsonString.length() < 1) {
throw new IllegalArgumentException(
String.format("JSON file parsing failed %s", jsonString));
}

// validate the schema name
final String schemaName = getPropertyAsString(JSON_PROPERTY_SCHEMA_NAME, jsonDocument);
final String schemaName = getRequiredPropertyAsString(jsonDocument, JSON_PROPERTY_SCHEMA_NAME);
if (!schemaName.equals(JSON_SCHEMA_NAME)) {
throw new IllegalArgumentException(
String.format("Expected bundle schema name %s but found %s", JSON_SCHEMA_NAME, schemaName));
}

// validate the schema version
final String schemaVersion = getPropertyAsString(JSON_PROPERTY_SCHEMA_VERSION, jsonDocument);
final String schemaVersion = getRequiredPropertyAsString(jsonDocument, JSON_PROPERTY_SCHEMA_VERSION);
if (!schemaVersion.equals(JSON_SCHEMA_VERSION)) {
throw new IllegalArgumentException(String.format("Expected bundle schema version %s but found %s",
JSON_SCHEMA_VERSION, schemaVersion));
}
primaryContentType = getPropertyAsString(JSON_PROPERTY_PRIMARY, jsonDocument);
primaryContentType = getRequiredPropertyAsString(jsonDocument, JSON_PROPERTY_PRIMARY);

jsonDocument.keySet().forEach((String contentType) -> {
if (! (jsonDocument.get(contentType) instanceof JSONObject jsonDoc)) {
return;
}

jsonDocument.asJsonMap().forEach((String contentType, Json jsonDoc) -> {
if (!TOP_LEVEL_PROPERTIES.contains(contentType)) {
final Json format = jsonDoc.at(JSON_PROPERTY_FORMAT);
final String format = jsonDoc.optString(JSON_PROPERTY_FORMAT, null);
final IOPathResource ioPathResource = new IOPathResource(
ioPathConstructor.apply(getPropertyAsString(JSON_PROPERTY_PATH, jsonDoc)),
ioPathConstructor.apply(getRequiredPropertyAsString(jsonDoc, JSON_PROPERTY_PATH)),
contentType,
format == null ?
null :
getPropertyAsString(JSON_PROPERTY_FORMAT, jsonDoc));
jsonDoc.optString(JSON_PROPERTY_FORMAT, null));
resources.add(ioPathResource);
}
});
if (resources.isEmpty()) {
LOG.warn("Empty resource bundle found: ", jsonString);
}
} catch (Json.MalformedJsonException | java.lang.UnsupportedOperationException e) {
} catch (JSONException | UnsupportedOperationException e) {
throw new IllegalArgumentException(e);
}

return new Bundle(primaryContentType, resources);
}

// Simple pretty-printer to produce indented JSON strings from a Json document. Note that
// this is not generalized and will only work on Json documents produced by BundleJSON::toJSON.
private static String prettyPrintJSON(final Json jsonDocument) {
final StringBuilder sb = new StringBuilder();
final String TOP_LEVEL_PROPERTY_FORMAT = " \"%s\":\"%s\"";

try {
sb.append("{\n");

// schema name
final String schemaName = getPropertyAsString(JSON_PROPERTY_SCHEMA_NAME, jsonDocument);
sb.append(String.format(TOP_LEVEL_PROPERTY_FORMAT, JSON_PROPERTY_SCHEMA_NAME, schemaName));
sb.append(",\n");

// schema version
final String schemaVersion = getPropertyAsString(JSON_PROPERTY_SCHEMA_VERSION, jsonDocument);
sb.append(String.format(TOP_LEVEL_PROPERTY_FORMAT, JSON_PROPERTY_SCHEMA_VERSION, schemaVersion));
sb.append(",\n");

// primary
final String primary = getPropertyAsString(JSON_PROPERTY_PRIMARY, jsonDocument);
sb.append(String.format(TOP_LEVEL_PROPERTY_FORMAT, JSON_PROPERTY_PRIMARY, primary));
sb.append(",\n");

final List<String> formattedResources = new ArrayList<>();
jsonDocument.asJsonMap().forEach((String contentType, Json jsonDoc) -> {
if (!TOP_LEVEL_PROPERTIES.contains(contentType)) {
final Json format = jsonDoc.at(JSON_PROPERTY_FORMAT);
final StringBuilder resSB = new StringBuilder();
if (format != null) {
resSB.append(String.format("{\"%s\":\"%s\",\"%s\":\"%s\"}",
JSON_PROPERTY_PATH,
getPropertyAsString(JSON_PROPERTY_PATH, jsonDoc),
JSON_PROPERTY_FORMAT,
getPropertyAsString(JSON_PROPERTY_FORMAT, jsonDoc)));
} else {
resSB.append(String.format("{\"%s\":\"%s\"}",
JSON_PROPERTY_PATH,
getPropertyAsString(JSON_PROPERTY_PATH, jsonDoc)));
}
formattedResources.add(String.format(" \"%s\":%s", contentType, resSB.toString()));
}
});
sb.append(formattedResources.stream().collect(Collectors.joining(",\n", "", "\n")));
sb.append("}\n");
} catch (Json.MalformedJsonException | java.lang.UnsupportedOperationException e) {
throw new IllegalArgumentException(e);
}

return sb.toString();
}

// return the value of propertyName from jsonDocument as a String value
private static String getPropertyAsString(final String propertyName, final Json jsonDocument) {
final Json propertyValue = jsonDocument.at(propertyName);
private static String getRequiredPropertyAsString(JSONObject jsonDocument, String propertyName) {
final String propertyValue = jsonDocument.optString(propertyName, null);
if (propertyValue == null) {
throw new IllegalArgumentException(
String.format("JSON bundle is missing the required property %s (%s)",
propertyName,
jsonDocument.toString()));
} else if (!propertyValue.isString()) {
throw new IllegalArgumentException(
String.format("Expected string value for bundle property %s but found %s",
propertyName,
propertyValue.toString()));
jsonDocument));
}
return propertyValue.asString();
}

return propertyValue;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package htsjdk.samtools.util.htsget;


import org.json.JSONObject;

/**
* Class allowing deserialization from json htsget error response, as defined in https://samtools.github.io/hts-specs/htsget.html
*
Expand Down Expand Up @@ -30,17 +32,15 @@ public String getMessage() {
}

public static HtsgetErrorResponse parse(final String s) {
final mjson.Json j = mjson.Json.read(s);
final mjson.Json htsget = j.at("htsget");
final JSONObject j = new JSONObject(s);
final JSONObject htsget = j.optJSONObject("htsget");
if (htsget == null) {
throw new IllegalStateException(new HtsgetMalformedResponseException("No htsget key found in response"));
}

final mjson.Json errorJson = htsget.at("error");
final mjson.Json messageJson = htsget.at("message");
final String errorJson = htsget.optString("error", null);
final String messageJson = htsget.optString("message", null);

return new HtsgetErrorResponse(
errorJson == null ? null : errorJson.asString(),
messageJson == null ? null : messageJson.asString());
return new HtsgetErrorResponse(errorJson, messageJson);
}
}
29 changes: 15 additions & 14 deletions src/main/java/htsjdk/samtools/util/htsget/HtsgetPOSTRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import htsjdk.samtools.SAMRecord;
import htsjdk.samtools.util.Locatable;
import htsjdk.samtools.util.RuntimeIOException;
import mjson.Json;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.IOException;
import java.net.HttpURLConnection;
Expand Down Expand Up @@ -134,41 +135,41 @@ public URI toURI() {
return this.getEndpoint();
}

public mjson.Json queryBody() {
final mjson.Json postBody = Json.object();
public JSONObject queryBody() {
final JSONObject postBody = new JSONObject();
if (this.format != null) {
postBody.set("format", this.getFormat().toString());
postBody.put("format", this.getFormat().toString());
}
if (this.dataClass != null) {
postBody.set("class", this.getDataClass().toString());
postBody.put("class", this.getDataClass().toString());
}
if (!this.fields.isEmpty()) {
postBody.set(
postBody.put(
"fields",
Json.array(this.getFields().stream()
new JSONArray(this.getFields().stream()
.map(HtsgetRequestField::toString)
.toArray())
);
}
if (!this.tags.isEmpty()) {
postBody.set("tags", Json.array(this.getTags().toArray()));
postBody.put("tags", new JSONArray(this.getTags().toArray()));
}
if (!this.notags.isEmpty()) {
postBody.set("notags", Json.array(this.getNoTags().toArray()));
postBody.put("notags", new JSONArray(this.getNoTags().toArray()));
}
if (!this.intervals.isEmpty()) {
postBody.set("regions", Json.array(
postBody.put("regions", new JSONArray(
this.intervals.stream()
.map(interval -> {
final mjson.Json intervalJson = Json.object();
final JSONObject intervalJson = new JSONObject();
if (interval != null && interval.getContig() != null) {
intervalJson.set("referenceName", interval.getContig());
intervalJson.put("referenceName", interval.getContig());
// Do not insert start and end for unmapped reads or if we are requesting the entire contig
if (!interval.getContig().equals(SAMRecord.NO_ALIGNMENT_REFERENCE_NAME)) {
// getStart() - 1 is necessary as GA4GH standards use 0-based coordinates while Locatables are 1-based
intervalJson.set("start", interval.getStart() - 1);
intervalJson.put("start", interval.getStart() - 1);
if (interval.getEnd() != Integer.MAX_VALUE && interval.getEnd() != -1) {
intervalJson.set("end", interval.getEnd());
intervalJson.put("end", interval.getEnd());
}
}
}
Expand Down
43 changes: 25 additions & 18 deletions src/main/java/htsjdk/samtools/util/htsget/HtsgetResponse.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package htsjdk.samtools.util.htsget;

import htsjdk.samtools.util.RuntimeIOException;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
* Class allowing deserialization from json htsget response, as defined in https://samtools.github.io/hts-specs/htsget.html
Expand Down Expand Up @@ -98,32 +104,32 @@ public InputStream getData() {
* @param blockJson json value representing a block
* @return parsed block object
*/
public static Block parse(final mjson.Json blockJson) {
final mjson.Json uriJson = blockJson.at("url");
public static Block parse(final JSONObject blockJson) {
final String uriJson = blockJson.optString("url", null);
if (uriJson == null) {
throw new HtsgetMalformedResponseException("No URI found in Htsget data block: " +
blockJson.toString().substring(0, Math.min(100, blockJson.toString().length())));
}
final URI uri;
try {
uri = new URI(uriJson.asString());
uri = new URI(uriJson);
} catch (final URISyntaxException e) {
throw new HtsgetMalformedResponseException("Could not parse URI in Htsget data block: " + uriJson.asString(), e);
throw new HtsgetMalformedResponseException("Could not parse URI in Htsget data block: " + uriJson, e);
}

final mjson.Json dataClassJson = blockJson.at("class");
final String dataClassJson = blockJson.optString("class", null);
final HtsgetClass dataClass = dataClassJson == null
? null
: HtsgetClass.valueOf(dataClassJson.asString().toLowerCase());
: HtsgetClass.valueOf(dataClassJson.toLowerCase());


final mjson.Json headersJson = blockJson.at("headers");
final JSONObject headersJson = blockJson.optJSONObject("headers");
final Map<String, String> headers = headersJson == null
? null
: headersJson.asJsonMap().entrySet().stream()
: headersJson.toMap().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().asString()
e -> e.getValue().toString()
));

return new Block(uri, headers, dataClass);
Expand Down Expand Up @@ -161,28 +167,29 @@ public String getMd5() {
* @return parsed HtsgetResponse object
*/
public static HtsgetResponse parse(final String s) {
final mjson.Json j = mjson.Json.read(s);
final mjson.Json htsget = j.at("htsget");
final JSONObject j = new JSONObject(s);
final JSONObject htsget = j.optJSONObject("htsget");
if (htsget == null) {
throw new HtsgetMalformedResponseException("No htsget key found in response");
}

final mjson.Json md5Json = htsget.at("md5");
final mjson.Json formatJson = htsget.at("format");
final String md5Json = htsget.optString("md5", null);
final String formatJson = htsget.optString("format", null);

final mjson.Json blocksJson = htsget.at("urls");
final JSONArray blocksJson = htsget.optJSONArray("urls");
if (blocksJson == null) {
throw new HtsgetMalformedResponseException("No urls field found in Htsget Response");
}

final List<Block> blocks = blocksJson.asJsonList().stream()
final List<Block> blocks = IntStream.range(0, blocksJson.length())
.mapToObj(blocksJson::getJSONObject)
.map(Block::parse)
.collect(Collectors.toList());

return new HtsgetResponse(
formatJson == null ? null : HtsgetFormat.valueOf(formatJson.asString().toUpperCase()),
formatJson == null ? null : HtsgetFormat.valueOf(formatJson.toUpperCase()),
blocks,
md5Json == null ? null : md5Json.asString()
md5Json
);
}

Expand Down
Loading