Skip to content

Commit bfbef70

Browse files
authored
feat: BACK-2316 add a simple method to detect TDFs (#111)
* just validate that we have a zip file with the right entries * add `InvalidZipException` so that we can catch it
1 parent 36a29df commit bfbef70

File tree

4 files changed

+117
-5
lines changed

4 files changed

+117
-5
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.opentdf.platform.sdk;
2+
3+
public class InvalidZipException extends RuntimeException {
4+
public InvalidZipException(String message) {
5+
super(message);
6+
}
7+
}

sdk/src/main/java/io/opentdf/platform/sdk/SDK.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@
1313
import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc;
1414
import io.opentdf.platform.policy.subjectmapping.SubjectMappingServiceGrpc.SubjectMappingServiceFutureStub;
1515
import io.opentdf.platform.sdk.nanotdf.NanoTDFType;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
1618

1719
import javax.net.ssl.TrustManager;
20+
import java.io.IOException;
21+
import java.nio.channels.SeekableByteChannel;
1822
import java.util.Optional;
1923

2024
/**
@@ -26,6 +30,8 @@ public class SDK implements AutoCloseable {
2630
private final TrustManager trustManager;
2731
private final ClientInterceptor authInterceptor;
2832

33+
private static final Logger log = LoggerFactory.getLogger(SDK.class);
34+
2935
@Override
3036
public void close() throws Exception {
3137
services.close();
@@ -112,4 +118,25 @@ public Optional<ClientInterceptor> getAuthInterceptor() {
112118
public Services getServices() {
113119
return this.services;
114120
}
121+
122+
/**
123+
* Checks to see if this has the structure of a Z-TDF in that it is a zip file containing
124+
* a `manifest.json` and a `0.payload`
125+
* @param channel A channel containing the bytes of the potential Z-TDF
126+
* @return `true` if
127+
*/
128+
public static boolean isTDF(SeekableByteChannel channel) {
129+
ZipReader zipReader;
130+
try {
131+
zipReader = new ZipReader(channel);
132+
} catch (IOException | InvalidZipException e) {
133+
return false;
134+
}
135+
var entries = zipReader.getEntries();
136+
if (entries.size() != 2) {
137+
return false;
138+
}
139+
return entries.stream().anyMatch(e -> "0.manifest.json".equals(e.getName()))
140+
&& entries.stream().anyMatch(e -> "0.payload".equals(e.getName()));
141+
}
115142
}

sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ CentralDirectoryRecord readEndOfCentralDirectory() throws IOException {
8484
}
8585

8686
if (eoCDRStart < 0) {
87-
throw new RuntimeException("Didn't find the end of central directory");
87+
throw new InvalidZipException("Didn't find the end of central directory");
8888
}
8989

9090
short diskNumber = readShort();
@@ -109,7 +109,7 @@ private CentralDirectoryRecord extractZIP64CentralDirectoryInfo() throws IOExcep
109109
// buffer's position at the start of the Central Directory
110110
int signature = readInt();
111111
if (signature != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) {
112-
throw new RuntimeException("Invalid Zip64 End of Central Directory Record Signature");
112+
throw new InvalidZipException("Invalid Zip64 End of Central Directory Record Signature");
113113
}
114114

115115
int centralDirectoryDiskNumber = readInt();
@@ -119,7 +119,7 @@ private CentralDirectoryRecord extractZIP64CentralDirectoryInfo() throws IOExcep
119119
zipChannel.position(offsetToEndOfCentralDirectory);
120120
int sig = readInt();
121121
if (sig != ZIP_64_END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
122-
throw new RuntimeException("Invalid");
122+
throw new InvalidZipException("Invalid");
123123
}
124124
long sizeOfEndOfCentralDirectoryRecord = readLong();
125125
short versionMadeBy = readShort();
@@ -152,7 +152,7 @@ public String getName() {
152152
public InputStream getData() throws IOException {
153153
zipChannel.position(offsetToLocalHeader);
154154
if (readInt() != LOCAL_FILE_HEADER_SIGNATURE) {
155-
throw new RuntimeException("Invalid Local Header Signature");
155+
throw new InvalidZipException("Invalid Local Header Signature");
156156
}
157157
zipChannel.position(zipChannel.position()
158158
+ Short.BYTES
@@ -218,7 +218,7 @@ public int read(byte[] b, int off, int len) throws IOException {
218218
public Entry readCentralDirectoryFileHeader() throws IOException {
219219
int signature = readInt();
220220
if (signature != CENTRAL_FILE_HEADER_SIGNATURE) {
221-
throw new RuntimeException("Invalid Central Directory File Header Signature");
221+
throw new InvalidZipException("Invalid Central Directory File Header Signature");
222222
}
223223
short versionMadeBy = readShort();
224224
short versionNeededToExtract = readShort();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.opentdf.platform.sdk;
2+
3+
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.io.IOException;
7+
import java.nio.ByteBuffer;
8+
import java.nio.channels.SeekableByteChannel;
9+
import java.util.Random;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
class SDKTest {
14+
15+
@Test
16+
void testExaminingValidZTDF() throws IOException {
17+
try (var ztdf = SDKTest.class.getClassLoader().getResourceAsStream("sample.txt.tdf")) {
18+
assert ztdf != null;
19+
var chan = new SeekableInMemoryByteChannel(ztdf.readAllBytes());
20+
assertThat(SDK.isTDF(chan)).isTrue();
21+
}
22+
}
23+
24+
@Test
25+
void testExaminingInvalidFile() {
26+
var chan = new SeekableByteChannel() {
27+
@Override
28+
public boolean isOpen() {
29+
return false;
30+
}
31+
32+
@Override
33+
public void close() {
34+
35+
}
36+
37+
@Override
38+
public int read(ByteBuffer dst) {
39+
return 0;
40+
}
41+
42+
@Override
43+
public int write(ByteBuffer src) {
44+
return 0;
45+
}
46+
47+
@Override
48+
public long position() {
49+
return 0;
50+
}
51+
52+
@Override
53+
public SeekableByteChannel position(long newPosition) {
54+
return this;
55+
}
56+
57+
@Override
58+
public long size() {
59+
return 0;
60+
}
61+
62+
@Override
63+
public SeekableByteChannel truncate(long size) {
64+
return null;
65+
}
66+
};
67+
68+
assertThat(SDK.isTDF(chan)).isFalse();
69+
}
70+
71+
@Test
72+
void testReadingRandomBytes() {
73+
var tdf = new byte[2023];
74+
new Random().nextBytes(tdf);
75+
76+
assertThat(SDK.isTDF(new SeekableInMemoryByteChannel(tdf))).isFalse();
77+
}
78+
}

0 commit comments

Comments
 (0)