NAME

Archive::Asar - list/extract/create Electron ASAR (Atom Shell Archive) files

SYNOPSIS

use Archive::Asar;

my $asar = Archive::Asar->new_from_file('app.asar');       # or
my $asar = Archive::Asar->new_from_string($blob);          # or
my $asar = Archive::Asar->new_from_fh(\*STDIN, '(stdin)'); # or
my $asar = Archive::Asar->new_empty;

my $index_structure = $asar->index_raw;

my $info = $asar->get_entry('file/foo.txt');

$asar->extract_to($target_directory);

$asar->remove($path);

$asar->add_directory($path);
$asar->add_link($path, $target);
$asar->add_file($path, $contents, $executable);

$asar->ingest($path, $file);

$asar->write_to_fh($fh);
$asar->write_to_file('output.asar');

DESCRIPTION

Archive::Asar provides an object-oriented interface for handling Electron .asar files ("Atom Shell Archive").

In the following description, a $path parameter refers to a member of the archive. It is either a reference to an array of path components (e.g. ['foo', 'bar', 'hello.txt']) or a string with path components separated by / (e.g. 'foo/bar/hello.txt') or (for Windows compatibility) by \ (e.g. 'foo\\bar\\hello.txt').

Use an empty path ([] or '') to refer to the root level of the archive, which is always a directory.

Constructors

new_from_fh

Usage: my $asar = Archive::Asar->new_from_fh($handle, $name);

Usage: my $asar = Archive::Asar->new_from_fh($handle);

$handle is the filehandle to read from (must be seekable, i.e. not a pipe, terminal, socket, etc). $name is the name of the archive (used in error messages); it defaults to '(fh)'.

$handle need not be a built-in Perl filehandle; it can be any object that responds to the required methods seek, tell, and read. If it is a filehandle, it needs to read binary data verbatim (without decoding), i.e. you should open it in <:raw mode or call binmode on it (see "binmode FILEHANDLE" in perlfunc.

Returns the initialized object or throws an exception on error.

new_from_file

Usage: my $asar = Archive::Asar->new_from_file($file);

Like "new_from_fh", but opens $file first (and automatically uses $file as the name of the archive in error messages).

new_from_string

Usage: my $asar = Archive::Asar->new_from_string($blob, $name);

Usage: my $asar = Archive::Asar->new_from_string($blob);

Like "new_from_fh", but reads the archive data directly from a byte string, not a filehandle. $name defaults to '(buffer)'.

new_empty

Usage: my $asar = Archive::Asar->new_empty($name);

Usage: my $asar = Archive::Asar->new_empty();

Creates an empty in-memory archive. $name is the name of the archive (used in error messages); it defaults to '(new)'.

Methods

index_raw

Usage: my $index = $asar->index_raw;

Returns the parsed index data structure stored as JSON in the ASAR file.

The returned data structure must not be modified.

get_entry

Usage: my $entry = $asar->get_entry($path);

Returns information about the archive member stored at $path.

If the specified path doesn't exist or anything else goes wrong, an exception is thrown.

The return value is a hash reference with a type field. There are three possible types:

directory

{ type => 'directory', entries => [...], unpacked? => true }

An entry of type directory has an entries field (an arrayref of strings) listing the entries in that directory. It may also have an unpacked field (normally set to true if present).

{ type => 'link', target => '...', unpacked? => true }

An entry of type link has a target field (a string) specifying the target of the symbolic link.

Beware: The target field is not validated! It may be any string provided by the archive. It may refer to a file outside of the archive or it may not be a valid filename at all.

file

{ type => 'file', unpacked => true }

{ type => 'file', contents => '...', executable? => true, integrity? => {...} }

An entry of type file has either a true unpacked field, indicating that the file is stored externally (outside of the packed archive), or a contents field giving the contents of the file as a string.

In the latter case, it may also have an executable field, which (if true) indicates that the file is executable, and/or an integrity field, which contains checksum information in the following format:

{
    algorithm => 'SHA256',
    hash      => '...', # lowercase hex
    blockSize => ...,
    blocks    => [...],
}

algorithm is the hashing algorithm to use (currently always SHA256; see Digest::SHA). hash is the result of hashing the file contents (in lowercase hex format, two hex digits per byte).

blockSize is an integer. The blocks field is an array of the results of splitting the file contents into chunks of (at most) blockSize bytes and hashing each chunk separately, in the same format as hash.

For example, an empty file may appear as:

{
    type      => 'file',
    contents  => '',
    integrity => {
        algorithm => 'SHA256',
        hash      => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
        blockSize => 0x400000,
        blocks    => ['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'],
    },
}
extract_to

Usage: $asar->extract_to($target_directory, $options = {});

Extracts the contents of the archive into the given $target_directory, which is automatically created if it doesn't exist yet.

The $options argument is optional, but must be a hash reference if given. Currently only one option is supported:

unpacked_dir

If unpacked_dir is set to a defined value, files marked unpacked in the archive will be read from this directory (and copied to $target_directory).

Otherwise unpacked files will be skipped when extracting.

remove

Usage: my $removed = $asar->remove($path);

Removes the specified path from the archive. If the path refers to a directory, everything in it is removed as well (like rm -r).

The last component of the specified path need not exist. If it doesn't exist, remove does nothing and returns false; otherwise it returns true.

add_directory

Usage: $asar->add_directory($path);

Creates a directory at the specified path.

Does nothing if the specified path already exists. Non-existent intermediate directories in $path are created automatically.

my $asar= Archive::Asar->new_empty;
$asar->add_directory('foo/bar/baz');
# Automatically does
#   $asar->add_directory('foo');
#   $asar->add_directory('foo/bar');
#   $asar->add_directory('foo/bar/baz');

Usage: $asar->add_link($path, $target);

Creates a symbolic link at the specified path pointing to $target, which must be a string.

Throws an error if the specified path already exists. Non-existent intermediate directories in $path are created automatically.

add_file

Usage: $asar->add_file($path, $contents, $executable);

Usage: $asar->add_file($path, $contents);

Creates a file at the specified path containing $contents, which must be a byte string. If $executable is passed as a true value, the archived file is marked as executable.

Throws an error if the specified path already exists. Non-existent intermediate directories in $path are created automatically.

ingest

Usage: $asar->ingest($path, $file);

Copies a file (or directory hierarchy, recursively) from $file into the archive under $path.

write_to_fh

Usage: $asar->write_to_fh($handle, $name);

Usage: $asar->write_to_fh($handle);

Writes ASAR data to the specified $handle. $name is the name of the archive (used in error messages); it defaults to '(fh)'.

$handle need not be a built-in Perl filehandle; it can be any object that responds to the required methods print and flush. If it is a filehandle, it needs to write binary data verbatim (without encoding), i.e. you should open it in >:raw mode or call binmode on it (see "binmode FILEHANDLE" in perlfunc.

write_to_file

Usage: $asar->write_to_file($file);

Like "write_to_fh", but opens $file first (and automatically uses $file as the name of the archive in error messages).

This method actually writes to a temporary file next to $file and renames it to $file at the end. This means if $file already exists, it will be overwritten and along with it any special modes/permissions it may have.

BUGS AND LIMITATIONS

  • Creating and modifying archives is not supported.

  • Support for "unpacked" archive members is rudimentary.

  • Tests are incomplete.

AUTHOR

Lukas Mai, <lmai at web.de>

COPYRIGHT & LICENSE

Copyright 2026 Lukas Mai.

This module is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.