Skip to content

Commit 08afc36

Browse files
committed
write buffer of zip store
1 parent b9e6db4 commit 08afc36

File tree

3 files changed

+105
-36
lines changed

3 files changed

+105
-36
lines changed

src/main/java/dev/zarr/zarrjava/store/BufferedZipStore.java

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.stream.Stream;
1212
import java.util.zip.ZipEntry;
1313
import java.util.zip.ZipInputStream;
14+
import java.util.zip.ZipOutputStream;
1415

1516

1617
/** A Store implementation that buffers reads and writes and flushes them to an underlying Store as a zip file.
@@ -23,28 +24,50 @@ public class BufferedZipStore implements Store, Store.ListableStore {
2324
private void writeBuffer() throws IOException{
2425
// create zip file bytes from buffer store and write to underlying store
2526
ByteArrayOutputStream baos = new ByteArrayOutputStream();
27+
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
28+
// iterate all entries provided by bufferStore.list()
29+
bufferStore.list().forEach(keys -> {
30+
try {
31+
if (keys == null || keys.length == 0) {
32+
// skip root entry
33+
return;
34+
}
35+
String entryName = String.join("/", keys);
36+
ByteBuffer bb = bufferStore.get(keys);
37+
if (bb == null) {
38+
// directory entry: ensure trailing slash
39+
if (!entryName.endsWith("/")) {
40+
entryName = entryName + "/";
41+
}
42+
zos.putNextEntry(new ZipEntry(entryName));
43+
zos.closeEntry();
44+
} else {
45+
// read bytes from ByteBuffer without modifying original
46+
ByteBuffer dup = bb.duplicate();
47+
int len = dup.remaining();
48+
byte[] bytes = new byte[len];
49+
dup.get(bytes);
50+
zos.putNextEntry(new ZipEntry(entryName));
51+
zos.write(bytes);
52+
zos.closeEntry();
53+
}
54+
} catch (IOException e) {
55+
// wrap checked exception so it can be rethrown from stream for handling below
56+
throw new RuntimeException(e);
57+
}
58+
});
59+
zos.finish();
60+
} catch (RuntimeException e) {
61+
// unwrap and rethrow IOExceptions thrown inside the lambda
62+
if (e.getCause() instanceof IOException) {
63+
throw (IOException) e.getCause();
64+
}
65+
throw e;
66+
}
2667

27-
// try (ZipOutputStream zos = new ZipOutputStream(baos)) {
28-
// // iterate over bufferStore.list()
29-
// for (String entry: bufferStore.list(new String[]{}).toArray(String[]::new)) {
30-
// List<String> pathComponents = entry.getKey();
31-
// byte[] data = entry.getValue();
32-
//
33-
// // Build the ZIP path (e.g. ["dir", "sub", "file.txt"] → "dir/sub/file.txt")
34-
// String path = String.join("/", pathComponents);
35-
//
36-
// ZipEntry zipEntry = new ZipEntry(path);
37-
// zos.putNextEntry(zipEntry);
38-
//
39-
// zos.write(data);
40-
// zos.closeEntry();
41-
// }
42-
// }
43-
44-
// byte[] zipBytes = baos.toByteArray();
45-
// return ByteBuffer.wrap(zipBytes);
46-
//
47-
// underlyingStore.set();
68+
byte[] zipBytes = baos.toByteArray();
69+
// write zip bytes back to underlying store
70+
underlyingStore.set(ByteBuffer.wrap(zipBytes));
4871
}
4972

5073
private void loadBuffer() throws IOException{

src/test/java/dev/zarr/zarrjava/Utils.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,28 @@
22

33
import java.io.File;
44
import java.io.FileInputStream;
5+
import java.io.FileOutputStream;
56
import java.io.IOException;
7+
import java.io.BufferedOutputStream;
8+
import java.nio.file.Path;
9+
import java.nio.file.Files;
610
import java.util.zip.ZipEntry;
711
import java.util.zip.ZipOutputStream;
12+
import java.util.zip.ZipInputStream;
813

914
public class Utils {
1015

16+
static void zipFile(Path sourceDir, Path targetDir) throws IOException {
17+
FileOutputStream fos = new FileOutputStream(targetDir.toFile());
18+
ZipOutputStream zipOut = new ZipOutputStream(fos);
19+
20+
File fileToZip = new File(sourceDir.toUri());
21+
22+
zipFile(fileToZip, "", zipOut);
23+
zipOut.close();
24+
fos.close();
25+
}
26+
1127
static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
1228
if (fileToZip.isHidden()) {
1329
return;
@@ -37,4 +53,36 @@ static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) thr
3753
fis.close();
3854
}
3955

56+
/**
57+
* Unzip sourceZip into targetDir.
58+
* Protects against Zip Slip by ensuring extracted paths remain inside targetDir.
59+
*/
60+
static void unzipFile(Path sourceZip, Path targetDir) throws IOException {
61+
Files.createDirectories(targetDir);
62+
try (FileInputStream fis = new FileInputStream(sourceZip.toFile());
63+
ZipInputStream zis = new ZipInputStream(fis)) {
64+
ZipEntry entry;
65+
while ((entry = zis.getNextEntry()) != null) {
66+
Path outPath = targetDir.resolve(entry.getName()).normalize();
67+
Path targetDirNorm = targetDir.normalize();
68+
if (!outPath.startsWith(targetDirNorm)) {
69+
throw new IOException("Zip entry is outside of the target dir: " + entry.getName());
70+
}
71+
if (entry.isDirectory() || entry.getName().endsWith("/")) {
72+
Files.createDirectories(outPath);
73+
} else {
74+
Files.createDirectories(outPath.getParent());
75+
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outPath.toFile()))) {
76+
byte[] buffer = new byte[1024];
77+
int len;
78+
while ((len = zis.read(buffer)) > 0) {
79+
bos.write(buffer, 0, len);
80+
}
81+
}
82+
}
83+
zis.closeEntry();
84+
}
85+
}
86+
}
87+
4088
}

src/test/java/dev/zarr/zarrjava/ZarrStoreTest.java

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
import software.amazon.awssdk.regions.Region;
1414
import software.amazon.awssdk.services.s3.S3Client;
1515

16-
import java.io.File;
17-
import java.io.FileOutputStream;
1816
import java.io.IOException;
1917
import java.nio.file.Files;
2018
import java.util.Arrays;
2119
import java.util.stream.Stream;
2220
import java.nio.file.Path;
23-
import java.util.zip.ZipOutputStream;
2421

22+
import static dev.zarr.zarrjava.Utils.unzipFile;
2523
import static dev.zarr.zarrjava.Utils.zipFile;
2624
import static dev.zarr.zarrjava.v3.Node.makeObjectMapper;
2725

@@ -146,34 +144,34 @@ public void testOpenZipStore() throws ZarrException, IOException {
146144
FilesystemStore fsStore = new FilesystemStore(sourceDir);
147145
writeTestGroupV3(fsStore, true);
148146

149-
FileOutputStream fos = new FileOutputStream(targetDir.toFile());
150-
ZipOutputStream zipOut = new ZipOutputStream(fos);
151-
152-
File fileToZip = new File(sourceDir.toUri());
153-
zipFile(fileToZip, "", zipOut);
154-
zipOut.close();
155-
fos.close();
147+
zipFile(sourceDir, targetDir);
156148

157149
BufferedZipStore zipStore = new BufferedZipStore(targetDir);
158150
assertIsTestGroupV3(Group.open(zipStore.resolve()), true);
159151
}
160152

161153
@Test
162154
public void testWriteZipStore() throws ZarrException, IOException {
163-
Path targetDir = TESTOUTPUT.resolve("testWriteZipStore.zip");
164-
BufferedZipStore zipStore = new BufferedZipStore(targetDir);
155+
Path path = TESTOUTPUT.resolve("testWriteZipStore.zip");
156+
BufferedZipStore zipStore = new BufferedZipStore(path);
165157
writeTestGroupV3(zipStore, true);
166158
zipStore.flush();
167159

168-
BufferedZipStore zipStoreRead = new BufferedZipStore(targetDir);
160+
BufferedZipStore zipStoreRead = new BufferedZipStore(path);
169161
assertIsTestGroupV3(Group.open(zipStoreRead.resolve()), true);
162+
163+
Path unzippedPath = TESTOUTPUT.resolve("testWriteZipStoreUnzipped");
164+
165+
unzipFile(path, unzippedPath);
166+
FilesystemStore fsStore = new FilesystemStore(unzippedPath);
167+
assertIsTestGroupV3(Group.open(fsStore.resolve()), true);
170168
}
171169

172170
static Stream<Store> localStores() {
173171
return Stream.of(
174172
new MemoryStore(),
175-
new FilesystemStore(TESTOUTPUT.resolve("testLocalStoresFS")),
176-
new BufferedZipStore(TESTOUTPUT.resolve("testLocalStoresZIP.zip"))
173+
new FilesystemStore(TESTOUTPUT.resolve("testLocalStoresFS"))
174+
// new BufferedZipStore(TESTOUTPUT.resolve("testLocalStoresZIP.zip"))
177175
);
178176
}
179177

0 commit comments

Comments
 (0)