Skip to content

Commit 442b499

Browse files
[GR-69033] Support reading non-compressed zip files in web-image
PullRequest: graal/21958
2 parents cb95633 + f7664da commit 442b499

File tree

5 files changed

+161
-8
lines changed

5 files changed

+161
-8
lines changed

web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/JS_JTT_Spec_Test.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package com.oracle.svm.hosted.webimage.test;
2626

27+
import com.oracle.svm.hosted.webimage.test.spec.JS_JTT_Zip;
2728
import org.junit.Test;
2829
import org.junit.runner.RunWith;
2930
import org.junit.runners.Suite;
@@ -45,6 +46,7 @@
4546
JS_JTT_JavaScriptBody.class,
4647
JS_JTT_JSAnnotation.class,
4748
JS_JTT_XHR.class,
49+
JS_JTT_Zip.class,
4850
})
4951
public class JS_JTT_Spec_Test extends JTTTestSuite {
5052

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.webimage.test.spec;
26+
27+
import com.oracle.svm.webimage.jtt.zip.InflatedZipTest;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
31+
import com.oracle.svm.hosted.webimage.test.util.JTTTestSuite;
32+
33+
public class JS_JTT_Zip extends JTTTestSuite {
34+
@BeforeClass
35+
public static void setupClass() {
36+
compileToJS(InflatedZipTest.class, "--initialize-at-build-time=InflatedZipTest");
37+
}
38+
39+
@Test
40+
public void readStoredTest() {
41+
testFileAgainstNoBuild(new String[]{"Read from ZIP: Hello, stored ZIP!"});
42+
}
43+
}

web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/WebImageFeature.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ public void afterRegistration(AfterRegistrationAccess access) {
274274
rci.initializeAtRunTime(WebImageFileSystem.class, "Static fields need to read system properties at runtime");
275275
rci.initializeAtRunTime(FileSystemInitializer.class, "Static fields need to read system properties at runtime");
276276
rci.initializeAtRunTime("java.nio.file.FileSystems$DefaultFileSystemHolder", "Parts of static initializer is substituted to inject custom FileSystemProvider");
277+
rci.initializeAtRunTime("java.util.zip.ZipFile$Source", "avoid initializing wrong file system");
277278

278279
for (Class<? extends JSObject> jsObjectSubclass : accessImpl.findSubclasses(JSObject.class)) {
279280
rci.initializeAtRunTime(jsObjectSubclass,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.webimage.jtt.zip;
26+
27+
import java.io.ByteArrayOutputStream;
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.util.zip.CRC32;
33+
import java.util.zip.ZipEntry;
34+
import java.util.zip.ZipFile;
35+
import java.util.zip.ZipOutputStream;
36+
37+
public class InflatedZipTest {
38+
static final byte[] compressed;
39+
static final String filename = "hello.txt";
40+
41+
static {
42+
byte[] content = "Hello, stored ZIP!".getBytes();
43+
CRC32 crc = new CRC32();
44+
crc.update(content);
45+
long crcValue = crc.getValue();
46+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
47+
try (ZipOutputStream zos = new ZipOutputStream(bos)) {
48+
ZipEntry entry = new ZipEntry(filename);
49+
entry.setMethod(ZipEntry.STORED); // non-deflated
50+
entry.setSize(content.length);
51+
entry.setCompressedSize(content.length);
52+
entry.setCrc(crcValue);
53+
zos.putNextEntry(entry);
54+
zos.write(content);
55+
zos.closeEntry();
56+
} catch (IOException e) {
57+
throw new RuntimeException(e);
58+
}
59+
compressed = bos.toByteArray();
60+
}
61+
62+
public static void main(String[] args) throws Exception {
63+
Path tempZip = Files.createTempFile("example", ".zip");
64+
Files.write(tempZip, compressed);
65+
try (ZipFile zipFile = new ZipFile(tempZip.toFile())) {
66+
ZipEntry entry = zipFile.getEntry(filename);
67+
try (InputStream is = zipFile.getInputStream(entry)) {
68+
String text = new String(is.readAllBytes());
69+
System.out.println("Read from ZIP: " + text);
70+
}
71+
}
72+
}
73+
}

web-image/src/com.oracle.svm.webimage/src/com/oracle/svm/webimage/substitute/system/WebImageIOSubstitutions.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.io.OutputStream;
3434
import java.nio.ByteBuffer;
3535
import java.nio.channels.FileChannel;
36+
import java.nio.file.FileSystem;
3637
import java.nio.file.Files;
3738
import java.nio.file.LinkOption;
3839
import java.nio.file.Path;
@@ -42,6 +43,7 @@
4243
import java.nio.file.spi.FileSystemProvider;
4344
import java.util.Map;
4445
import java.util.Objects;
46+
import java.util.Set;
4547

4648
import org.graalvm.nativeimage.ImageSingletons;
4749

@@ -345,20 +347,44 @@ public static Path createTempDirectory(String prefix, FileAttribute<?>... attrs)
345347
@TargetClass(java.io.RandomAccessFile.class)
346348
@SuppressWarnings("all")
347349
final class Target_java_io_RandomAccessFile_Web {
350+
@Alias static int O_RDWR;
351+
@Alias FileChannel channel;
348352

349353
@Substitute
350354
private void open0(String name, int mode) throws FileNotFoundException {
351-
throw new UnsupportedOperationException("RandomAccessFile.open0");
355+
Path path = Path.of(name);
356+
if (Files.notExists(path)) {
357+
throw new FileNotFoundException(name + " does not exist.");
358+
}
359+
if (Files.isDirectory(path)) {
360+
throw new FileNotFoundException(name + " is a directory.");
361+
}
362+
if ((mode & O_RDWR) != 0) {
363+
throw new UnsupportedOperationException("open RandomAccessFile with mode other than readonly.");
364+
}
365+
366+
try {
367+
Set<StandardOpenOption> options = Set.of(StandardOpenOption.READ);
368+
channel = FileChannel.open(Path.of(name), options);
369+
} catch (IOException e) {
370+
throw new RuntimeException(e);
371+
}
352372
}
353373

354374
@Substitute
355375
private int read0() throws IOException {
356-
throw new UnsupportedOperationException("RandomAccessFile.read0");
376+
ByteBuffer buffer = ByteBuffer.allocate(1);
377+
int result = channel.read(buffer);
378+
return buffer.get(0);
357379
}
358380

359381
@Substitute
360-
private int readBytes(byte[] b, int off, int len) throws IOException {
361-
throw new UnsupportedOperationException("RandomAccessFile.readBytes");
382+
private int readBytes0(byte[] b, int off, int len) throws IOException {
383+
ByteBuffer buf = ByteBuffer.allocate(len);
384+
int read = channel.read(buf);
385+
buf.flip();
386+
buf.get(b, off, Math.min(read, len));
387+
return read;
362388
}
363389

364390
@Substitute
@@ -373,17 +399,17 @@ private void writeBytes(byte[] b, int off, int len) throws IOException {
373399

374400
@Substitute
375401
public long getFilePointer() throws IOException {
376-
throw new UnsupportedOperationException("RandomAccessFile.getFilePointer");
402+
return channel.position();
377403
}
378404

379405
@Substitute
380406
private void seek0(long pos) throws IOException {
381-
throw new UnsupportedOperationException("RandomAccessFile.seek0");
407+
channel.position(pos);
382408
}
383409

384410
@Substitute
385411
public long length() throws IOException {
386-
throw new UnsupportedOperationException("RandomAccessFile.length");
412+
return channel.size();
387413
}
388414

389415
@Substitute
@@ -393,7 +419,7 @@ public void setLength(long newLength) throws IOException {
393419

394420
@Substitute
395421
private static void initIDs() {
396-
throw new UnsupportedOperationException("RandomAccessFile.initIDs");
422+
// do nothing
397423
}
398424
}
399425

@@ -422,6 +448,14 @@ public long size() {
422448

423449
}
424450

451+
@TargetClass(className = "sun.nio.fs.DefaultFileSystemProvider")
452+
final class Target_sun_nio_fs_DefaultFileSystemProvider {
453+
@Substitute
454+
public static FileSystem theFileSystem() {
455+
return WebImageNIOFileSystemProvider.INSTANCE.getFileSystem(null);
456+
}
457+
}
458+
425459
@TargetClass(className = "java.nio.file.TempFileHelper")
426460
final class Target_java_nio_file_TempFileHelper_Web {
427461

0 commit comments

Comments
 (0)