Packages

Simple in-memory tarball extraction in pure Gleam.

Current section

Files

Jump to
tartiflette src tartiflette.gleam
Raw

src/tartiflette.gleam

// IMPORTS ---------------------------------------------------------------------
import gleam/bit_array
import gleam/bool
import gleam/int
import gleam/list
import gleam/result
import gleam/string
// TYPES -----------------------------------------------------------------------
///
///
pub type Entry {
File(name: String, content: BitArray)
HardLink(name: String, target: String)
SymbolicLink(name: String, target: String)
CharacterDevice(name: String, major: Int, minor: Int)
BlockDevice(name: String, major: Int, minor: Int)
Directory(name: String)
Fifo(name: String)
}
// CONSTRUCTORS ----------------------------------------------------------------
/// Synchronously extract an uncompressed tar archive in-memory.
///
pub fn extract(tarball: BitArray) -> Result(List(Entry), Nil) {
do_extract(tarball, [])
}
fn do_extract(
tarball: BitArray,
files: List(Entry),
) -> Result(List(Entry), Nil) {
case tarball {
<<>> | <<0:1024>> -> Ok(list.reverse(files))
<<
// Original header fields ------------------------------------------------
file_path:bytes-size(100),
_file_mode:bytes-size(8),
_owner_user_id:bytes-size(8),
_owner_group_id:bytes-size(8),
file_size:bytes-size(12),
_file_mtime:bytes-size(12),
_header_checksum:bytes-size(8),
file_type:bytes-size(1),
link_path:bytes-size(100),
// Additional POSIX/USTAR header fields ----------------------------------
_magic_bytes:bytes-size(6),
_version:bytes-size(2),
_owner_user_name:bytes-size(32),
_owner_group_name:bytes-size(32),
device_major_number:bytes-size(8),
device_minor_number:bytes-size(8),
_prefix:bytes-size(155),
// Padding ---------------------------------------------------------------
_padding:bytes-size(12),
// Content ---------------------------------------------------------------
rest:bits,
>> -> {
use name <- result.try(null_terminated_string(file_path, <<>>))
use <- bool.guard(name == "", Ok(list.reverse(files)))
use size <- result.try(octal_string(file_size))
let padding = 512 + { -size % 512 }
use #(content, rest) <- result.try(case rest {
<<content:bytes-size(size), _:bytes-size(padding), rest:bits>> ->
Ok(#(content, rest))
_ -> Error(Nil)
})
case file_type {
// Standard File -------------------------------------------------------
<<"0">> | <<0>> -> do_extract(rest, [File(name:, content:), ..files])
// Hard Link -----------------------------------------------------------
<<"1">> -> {
use target <- result.try(null_terminated_string(link_path, <<>>))
do_extract(rest, [HardLink(name:, target:), ..files])
}
// Symbolic Link -------------------------------------------------------
<<"2">> -> {
use target <- result.try(null_terminated_string(link_path, <<>>))
do_extract(rest, [SymbolicLink(name:, target:), ..files])
}
// Character Device ----------------------------------------------------
<<"3">> -> {
use major <- result.try(octal_string(device_major_number))
use minor <- result.try(octal_string(device_minor_number))
do_extract(rest, [CharacterDevice(name:, major:, minor:), ..files])
}
// Block Device --------------------------------------------------------
<<"4">> -> {
use major <- result.try(octal_string(device_major_number))
use minor <- result.try(octal_string(device_minor_number))
do_extract(rest, [BlockDevice(name:, major:, minor:), ..files])
}
// Directory -----------------------------------------------------------
<<"5">> -> do_extract(rest, [Directory(name:), ..files])
// FIFO ----------------------------------------------------------------
<<"6">> -> do_extract(rest, [Fifo(name:), ..files])
// Contiguous File -----------------------------------------------------
<<"7">> -> do_extract(rest, [File(name:, content:), ..files])
_ -> Error(Nil)
}
}
_ -> Error(Nil)
}
}
fn null_terminated_string(
bytes: BitArray,
out: BitArray,
) -> Result(String, Nil) {
case bytes {
// It might be null-terminiated, it might not!
<<>> | <<0>> | <<0, _:bits>> -> bit_array.to_string(out)
<<char, rest:bits>> -> null_terminated_string(rest, <<out:bits, char>>)
_ -> Error(Nil)
}
}
fn octal_string(bytes: BitArray) -> Result(Int, Nil) {
null_terminated_string(bytes, <<>>)
// Hey so sometimes these are padded with spaces for some reason
|> result.map(string.trim)
|> result.try(int.base_parse(_, 8))
}