Skip to content
Merged
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
4 changes: 2 additions & 2 deletions src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitId = "46b0ccb80";
public static final String gitCommitId = "25b6fa935";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand All @@ -48,7 +48,7 @@ public final class Configuration {
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String buildTimestamp = "Apr 28 2026 18:01:32";
public static final String buildTimestamp = "Apr 28 2026 18:03:29";

// Prevent instantiation
private Configuration() {
Expand Down
73 changes: 64 additions & 9 deletions src/main/java/org/perlonjava/runtime/perlmodule/ArchiveZip.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ public class ArchiveZip extends PerlModuleBase {
private static final String FILENAME_KEY = "_filename";
private static final String COMMENT_KEY = "_zipfileComment";

/**
* Resolve a path string against Perl's notion of the current working
* directory (System "user.dir"), since Java's Paths.get does not honor
* Perl chdir() updates to user.dir.
*/
private static Path resolvePath(String name) {
Path p = Paths.get(name);
if (p.isAbsolute()) return p;
return Paths.get(System.getProperty("user.dir")).resolve(p);
}

private static Path resolvePath(String first, String... more) {
Path p = Paths.get(first, more);
if (p.isAbsolute()) return p;
return Paths.get(System.getProperty("user.dir")).resolve(p);
}

// Constants (matching Archive::Zip)
public static final int AZ_OK = 0;
public static final int AZ_STREAM_END = 1;
Expand Down Expand Up @@ -90,6 +107,7 @@ public static void initialize() {
az.registerMethod("versionNeededToExtract", null);
az.registerMethod("bitFlag", null);
az.registerMethod("fileComment", null);
az.registerMethod("extractToFileNamed", null);

// Constants
az.registerMethod("AZ_OK", null);
Expand Down Expand Up @@ -203,16 +221,17 @@ public static RuntimeList read(RuntimeArray args, int ctx) {
RuntimeArray members = getMembers(self);
members.undefine(); // Clear existing members

Path path = Paths.get(filename);
Path path = resolvePath(filename);
if (!Files.exists(path)) {
return new RuntimeScalar(AZ_IO_ERROR).getList();
}
String resolvedName = path.toString();

// Extract raw DOS timestamps from central directory
// (Java's ZipEntry uses extended timestamps when available)
java.util.Map<String, Long> rawDosTimestamps = extractRawDosTimestamps(filename);
java.util.Map<String, Long> rawDosTimestamps = extractRawDosTimestamps(resolvedName);

try (ZipFile zipFile = new ZipFile(filename)) {
try (ZipFile zipFile = new ZipFile(resolvedName)) {
// Store the zipfile comment
String comment = zipFile.getComment();
if (comment != null) {
Expand Down Expand Up @@ -399,7 +418,7 @@ public static RuntimeList writeToFileNamed(RuntimeArray args, int ctx) {
try {
RuntimeArray members = getMembers(self);

try (FileOutputStream fos = new FileOutputStream(filename);
try (FileOutputStream fos = new FileOutputStream(resolvePath(filename).toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {

for (int i = 0; i < members.size(); i++) {
Expand Down Expand Up @@ -577,7 +596,7 @@ public static RuntimeList addFile(RuntimeArray args, int ctx) {
String memberName = args.size() > 2 ? args.get(2).toString() : filename;

try {
Path path = Paths.get(filename);
Path path = resolvePath(filename);
if (!Files.exists(path)) {
return scalarUndef.getList();
}
Expand Down Expand Up @@ -704,13 +723,13 @@ public static RuntimeList extractMember(RuntimeArray args, int ctx) {
RuntimeScalar isDir = member.get("_isDirectory");
if (isDir != null && isDir.getBoolean()) {
// Create directory
Path path = Paths.get(destName);
Path path = resolvePath(destName);
Files.createDirectories(path);
} else {
// Extract file
RuntimeScalar contents = member.get("_contents");
if (contents != null) {
Path path = Paths.get(destName);
Path path = resolvePath(destName);
// Create parent directories if needed
Path parent = path.getParent();
if (parent != null) {
Expand Down Expand Up @@ -776,7 +795,7 @@ public static RuntimeList extractMemberWithoutPaths(RuntimeArray args, int ctx)

RuntimeScalar contents = member.get("_contents");
if (contents != null) {
Path destPath = Paths.get(destDir, baseName);
Path destPath = resolvePath(destDir, baseName);
Path parent = destPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
Expand All @@ -792,6 +811,42 @@ public static RuntimeList extractMemberWithoutPaths(RuntimeArray args, int ctx)
}
}

/**
* Member method: extract this member to a specified file name.
* Usage: $status = $member->extractToFileNamed($filename);
*/
public static RuntimeList extractToFileNamed(RuntimeArray args, int ctx) {
if (args.size() < 2) {
return new RuntimeScalar(AZ_ERROR).getList();
}

RuntimeHash member = args.get(0).hashDeref();
String destName = args.get(1).toString();

try {
RuntimeScalar isDir = member.get("_isDirectory");
if (isDir != null && isDir.getBoolean()) {
Path path = resolvePath(destName);
Files.createDirectories(path);
return new RuntimeScalar(AZ_OK).getList();
}

RuntimeScalar contents = member.get("_contents");
Path path = resolvePath(destName);
Path parent = path.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
byte[] data = contents != null
? contents.toString().getBytes(StandardCharsets.ISO_8859_1)
: new byte[0];
Files.write(path, data);
return new RuntimeScalar(AZ_OK).getList();
} catch (IOException e) {
return new RuntimeScalar(AZ_IO_ERROR).getList();
}
}

/**
* Extract all members to a directory.
* Usage: $status = $zip->extractTree('', 'dest/');
Expand Down Expand Up @@ -826,7 +881,7 @@ public static RuntimeList extractTree(RuntimeArray args, int ctx) {
destName = memberName.substring(root.length());
}

Path destPath = Paths.get(dest, destName);
Path destPath = resolvePath(dest, destName);

RuntimeScalar isDir = member.get("_isDirectory");
if (isDir != null && isDir.getBoolean()) {
Expand Down
Loading