Skip to content
Open
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 @@ -9,4 +9,8 @@ public class InvalidZipException extends RuntimeException {
public InvalidZipException(String message) {
super(message);
}

public InvalidZipException(String message, Throwable cause) {
super(message, cause);
}
}
11 changes: 11 additions & 0 deletions sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,17 @@

validateSegmentSizes();

try {
readSegments(outputStream, digest);
// the segment lengths are all the loop reads, so check a deflated payload ends there
tdfReader.finishPayload();
} finally {
// releases a deflated payload's inflater however the read ended
tdfReader.closePayload();
}
}

private void readSegments(OutputStream outputStream, MessageDigest digest) throws IOException {

Check failure on line 411 in sdk/src/main/java/io/opentdf/platform/sdk/TDF.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=opentdf_java-sdk&issues=AaFb8NKQZCJTiygsi_w7&open=AaFb8NKQZCJTiygsi_w7&pullRequest=424
for (Manifest.Segment segment : manifest.encryptionInformation.integrityInformation.segments) {
byte[] readBuf = new byte[(int) segment.encryptedSegmentSize];
int bytesRead = tdfReader.readPayloadBytes(readBuf);
Expand Down
80 changes: 76 additions & 4 deletions sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;

import static io.opentdf.platform.sdk.TDFWriter.TDF_MANIFEST_FILE_NAME;
import static io.opentdf.platform.sdk.TDFWriter.TDF_MANIFEST_FILE_NAME_SPEC;
Expand All @@ -20,7 +21,13 @@
public class TDFReader {

private final ZipReader.Entry manifestEntry;
private final InputStream payload;
private final ZipReader.Entry payloadEntry;
/**
* Opened by the first payload read rather than here, so reading only the manifest never
* allocates an inflater for a deflated payload. Released by {@link #closePayload()}.
*/
private InputStream payload;
private boolean payloadReleased = false;

public TDFReader(SeekableByteChannel tdf) throws SDKException, IOException {
// A zip may legally list the same name twice, and readers disagree about which copy
Expand All @@ -46,33 +53,98 @@ public TDFReader(SeekableByteChannel tdf) throws SDKException, IOException {
}

manifestEntry = manifest;
payload = entries.get(TDF_PAYLOAD_FILE_NAME).getData();
payloadEntry = entries.get(TDF_PAYLOAD_FILE_NAME);
// check the payload now, so one that can't be read (an unsupported compression method,
// or a deflated size over its budget) is refused before any key is requested -- but
// without opening it, since a caller may only want the manifest
payloadEntry.checkReadable();
}

String manifest() {
var out = new ByteArrayOutputStream();
try {
manifestEntry.getData().transferTo(out);
// closing releases the inflater when the manifest entry is deflated
try (var manifestData = manifestEntry.getData()) {
manifestData.transferTo(out);
} catch (IOException e) {
throw new SDKException("error retrieving manifest from zip file", e);
}

return out.toString(StandardCharsets.UTF_8);
}

/**
* Reads the next payload bytes into {@code buf}, opening the payload on the first call. A
* failed read releases the payload; once it has been released this reads nothing.
*/
int readPayloadBytes(byte[] buf) {
if (payloadReleased) {
return 0;
}
int totalRead = 0;
int nread;
boolean succeeded = false;
try {
if (payload == null) {
payload = payloadEntry.getData();
}
while (totalRead < buf.length && (nread = payload.read(buf, totalRead, buf.length - totalRead)) >= 0) {
totalRead += nread;
}
succeeded = true;
} catch (IOException e) {
throw new SDKException("error reading from payload in TDF", e);
} finally {
if (!succeeded) {
closePayload();
}
}
return totalRead;
}

/**
* Called once every segment has been read. A deflated payload must end exactly there: a
* further read has to find the end of the entry, which for a deflated entry also confirms
* the deflate stream ends at its declared size. A stored payload is not checked, as before.
* Releases the payload either way.
*
* @throws InvalidZipException if a deflated payload holds data past its last segment
*/
void finishPayload() {
try {
if (payloadEntry.getCompressionMethod() == ZipEntry.DEFLATED && !payloadReleased) {
if (payload == null) {
payload = payloadEntry.getData();
}
if (payload.read() != -1) {
throw new InvalidZipException("deflated entry [" + payloadEntry.getName()
+ "] holds data past the end of the TDF's last segment");
}
}
} catch (IOException e) {
throw new SDKException("error reading from payload in TDF", e);
} finally {
closePayload();
}
}

/**
* Releases the payload stream, and with it a deflated payload's inflater. Idempotent; after
* it, payload reads return nothing.
*/
void closePayload() {
payloadReleased = true;
if (payload == null) {
return;
}
try {
payload.close();
} catch (IOException e) {
throw new SDKException("error closing payload in TDF", e);
} finally {
payload = null;
}
}

PolicyObject readPolicyObject() {
String manifestJson = manifest();
Manifest manifest = Manifest.readManifest(manifestJson);
Expand Down
Loading
Loading