-module(packkit@tar).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/packkit/tar.gleam").
-export([format/0, new/0, add_file_checked/3, add_file/3, add_directory_checked/2, add_directory/2, add_symlink_checked/3, add_symlink/3, encode/1, decode_with_limits/2, decode/1]).
-export_type([pending_override/0, parsed_header/0, octal_class/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" USTAR (POSIX 1003.1-1988) tar archive encoder and decoder.\n"
"\n"
" The implementation is target-neutral and supports regular files,\n"
" directories, symbolic links, and hard links. The encoder rejects\n"
" names longer than the USTAR `prefix`/`name` split allows with a\n"
" typed archive error. The decoder additionally consumes GNU\n"
" `LongName`/`LongLink` extension entries (typeflags `L` and `K`),\n"
" PAX extended attribute headers (`x` and `g`) — extracting the\n"
" `path` / `linkpath` records so files emitted by\n"
" `tar --format=pax` keep their full path past the USTAR 100-char\n"
" limit — and skips the other PAX attribute keys.\n"
).
-type pending_override() :: {pending_override, binary(), binary()}.
-type parsed_header() :: {parsed_header,
binary(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
binary()}.
-type octal_class() :: {octal_digit, integer()} |
octal_space |
octal_terminator |
octal_invalid.
-file("src/packkit/tar.gleam", 34).
?DOC(" Tar archive format marker.\n").
-spec format() -> packkit@archive:archive_format().
format() ->
packkit@archive:tar().
-file("src/packkit/tar.gleam", 39).
?DOC(" Create an empty tar archive value.\n").
-spec new() -> packkit@archive:archive().
new() ->
packkit@archive:new(format()).
-file("src/packkit/tar.gleam", 44).
?DOC(" Add a regular file after checked path validation.\n").
-spec add_file_checked(packkit@archive:archive(), binary(), bitstring()) -> {ok,
packkit@archive:archive()} |
{error, packkit@entry:entry_error()}.
add_file_checked(Archive_value, Path, Body) ->
case packkit@entry:file_checked(Path, Body) of
{ok, Value} ->
{ok, packkit@archive:add(Archive_value, Value)};
{error, Err} ->
{error, Err}
end.
-file("src/packkit/tar.gleam", 56).
?DOC(" Panicking counterpart of `add_file_checked`.\n").
-spec add_file(packkit@archive:archive(), binary(), bitstring()) -> packkit@archive:archive().
add_file(Archive_value, Path, Body) ->
packkit@archive:add(Archive_value, packkit@entry:file(Path, Body)).
-file("src/packkit/tar.gleam", 65).
?DOC(" Add a directory after checked path validation.\n").
-spec add_directory_checked(packkit@archive:archive(), binary()) -> {ok,
packkit@archive:archive()} |
{error, packkit@entry:entry_error()}.
add_directory_checked(Archive_value, Path) ->
case packkit@entry:directory_checked(Path) of
{ok, Value} ->
{ok, packkit@archive:add(Archive_value, Value)};
{error, Err} ->
{error, Err}
end.
-file("src/packkit/tar.gleam", 76).
?DOC(" Panicking counterpart of `add_directory_checked`.\n").
-spec add_directory(packkit@archive:archive(), binary()) -> packkit@archive:archive().
add_directory(Archive_value, Path) ->
packkit@archive:add(Archive_value, packkit@entry:directory(Path)).
-file("src/packkit/tar.gleam", 84).
?DOC(" Add a symbolic link after checked path validation.\n").
-spec add_symlink_checked(packkit@archive:archive(), binary(), binary()) -> {ok,
packkit@archive:archive()} |
{error, packkit@entry:entry_error()}.
add_symlink_checked(Archive_value, Path, Target) ->
case packkit@entry:symlink_checked(Path, Target) of
{ok, Value} ->
{ok, packkit@archive:add(Archive_value, Value)};
{error, Err} ->
{error, Err}
end.
-file("src/packkit/tar.gleam", 96).
?DOC(" Panicking counterpart of `add_symlink_checked`.\n").
-spec add_symlink(packkit@archive:archive(), binary(), binary()) -> packkit@archive:archive().
add_symlink(Archive_value, Path, Target) ->
packkit@archive:add(Archive_value, packkit@entry:symlink(Path, Target)).
-file("src/packkit/tar.gleam", 118).
-spec reject_comment(packkit@archive:archive()) -> {ok, nil} |
{error, packkit@error:archive_error()}.
reject_comment(Archive_value) ->
case packkit@archive:comment(Archive_value) of
none ->
{ok, nil};
{some, _} ->
{error, {archive_comment_unsupported, <<"tar"/utf8>>}}
end.
-file("src/packkit/tar.gleam", 158).
-spec no_pending() -> pending_override().
no_pending() ->
{pending_override, <<""/utf8>>, <<""/utf8>>}.
-file("src/packkit/tar.gleam", 282).
-spec apply_pending(parsed_header(), pending_override()) -> parsed_header().
apply_pending(Header, Pending) ->
Name = case erlang:element(2, Pending) of
<<""/utf8>> ->
erlang:element(2, Header);
N ->
N
end,
Linkname = case erlang:element(3, Pending) of
<<""/utf8>> ->
erlang:element(9, Header);
L ->
L
end,
{parsed_header,
Name,
erlang:element(3, Header),
erlang:element(4, Header),
erlang:element(5, Header),
erlang:element(6, Header),
erlang:element(7, Header),
erlang:element(8, Header),
Linkname}.
-file("src/packkit/tar.gleam", 297).
?DOC(
" Read the body of a PAX 'x' / 'g' extended-attribute header.\n"
" Returns the raw bytes; the caller is responsible for parsing\n"
" the \"<length> <key>=<value>\\n\" records inside.\n"
).
-spec read_pax_body(bitstring(), integer(), integer()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
read_pax_body(Bytes, Start, Size) ->
case gleam_stdlib:bit_array_slice(Bytes, Start, Size) of
{ok, Chunk} ->
{ok, Chunk};
{error, _} ->
{error,
{archive_invalid, <<"truncated tar PAX extended header"/utf8>>}}
end.
-file("src/packkit/tar.gleam", 343).
?DOC(
" Parse one \"<length> <key>=<value>\\n\" PAX record off the front\n"
" of `text` and return (key, value, remaining_text).\n"
).
-spec extract_pax_record(binary()) -> {ok, {binary(), binary(), binary()}} |
{error, nil}.
extract_pax_record(Text) ->
gleam@result:'try'(
gleam@string:split_once(Text, <<" "/utf8>>),
fun(_use0) ->
{Length_str, After_space} = _use0,
gleam@result:'try'(
gleam_stdlib:parse_int(Length_str),
fun(Length) ->
Head_size = string:length(Length_str) + 1,
Inner_size = (Length - Head_size) - 1,
gleam@bool:guard(
Inner_size < 0,
{error, nil},
fun() ->
case gleam@string:slice(After_space, 0, Inner_size) of
<<""/utf8>> ->
{error, nil};
Body ->
gleam@result:'try'(
gleam@string:split_once(
Body,
<<"="/utf8>>
),
fun(_use0@1) ->
{Key, Value} = _use0@1,
Rest = gleam@string:slice(
After_space,
Inner_size + 1,
string:length(After_space)
),
{ok, {Key, Value, Rest}}
end
)
end
end
)
end
)
end
).
-file("src/packkit/tar.gleam", 324).
-spec apply_pax_records_loop(pending_override(), binary()) -> pending_override().
apply_pax_records_loop(Pending, Text) ->
case extract_pax_record(Text) of
{error, _} ->
Pending;
{ok, {Key, Value, Rest}} ->
Pending@1 = case Key of
<<"path"/utf8>> ->
{pending_override, Value, erlang:element(3, Pending)};
<<"linkpath"/utf8>> ->
{pending_override, erlang:element(2, Pending), Value};
_ ->
Pending
end,
apply_pax_records_loop(Pending@1, Rest)
end.
-file("src/packkit/tar.gleam", 314).
?DOC(
" Walk a PAX extended-header body and lift the keys we know how\n"
" to use (\"path\" overrides the next entry's name, \"linkpath\"\n"
" overrides the next entry's linkname). Records with other keys\n"
" are silently dropped — they don't change which bytes the entry\n"
" holds, only how POSIX-aware tools display its metadata.\n"
).
-spec apply_pax_records(pending_override(), bitstring()) -> pending_override().
apply_pax_records(Pending, Body) ->
case gleam@bit_array:to_string(Body) of
{ok, Text} ->
apply_pax_records_loop(Pending, Text);
{error, _} ->
Pending
end.
-file("src/packkit/tar.gleam", 387).
-spec trim_trailing_nul_size(bitstring(), integer()) -> integer().
trim_trailing_nul_size(Bytes, Size) ->
case Size of
0 ->
0;
N ->
case gleam_stdlib:bit_array_slice(Bytes, N - 1, 1) of
{ok, <<0>>} ->
trim_trailing_nul_size(Bytes, N - 1);
_ ->
N
end
end.
-file("src/packkit/tar.gleam", 364).
-spec read_string_body(bitstring(), integer(), integer(), binary()) -> {ok,
binary()} |
{error, packkit@error:archive_error()}.
read_string_body(Bytes, Start, Size, Label) ->
case gleam_stdlib:bit_array_slice(Bytes, Start, Size) of
{ok, Chunk} ->
Trimmed_size = trim_trailing_nul_size(Chunk, Size),
Name_bits@1 = case gleam_stdlib:bit_array_slice(
Chunk,
0,
Trimmed_size
) of
{ok, Name_bits} -> Name_bits;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"read_string_body"/utf8>>,
line => 374,
value => _assert_fail,
start => 11336,
'end' => 11402,
pattern_start => 11347,
pattern_end => 11360})
end,
case gleam@bit_array:to_string(Name_bits@1) of
{ok, Value} ->
{ok, Value};
{error, _} ->
{error,
{archive_invalid,
<<<<"tar "/utf8, Label/binary>>/binary,
" is not valid UTF-8"/utf8>>}}
end;
{error, _} ->
{error, {archive_invalid, <<"truncated tar "/utf8, Label/binary>>}}
end.
-file("src/packkit/tar.gleam", 398).
-spec check_member_limit(integer(), packkit@limit:limits()) -> {ok, nil} |
{error, packkit@error:archive_error()}.
check_member_limit(Count, Limits) ->
case Count > packkit@limit:max_members(Limits) of
true ->
{error, {archive_limit_exceeded, <<"max_members"/utf8>>, Count}};
false ->
{ok, nil}
end.
-file("src/packkit/tar.gleam", 529).
-spec entry_error_to_archive_error(packkit@entry:entry_error(), binary()) -> packkit@error:archive_error().
entry_error_to_archive_error(Err, Path) ->
case Err of
empty_path ->
{archive_entry_rejected, Path, <<"empty path"/utf8>>};
{absolute_path, _} ->
{archive_entry_rejected, Path, <<"absolute path"/utf8>>};
{path_traversal, _} ->
{archive_entry_rejected, Path, <<"path traversal"/utf8>>};
{windows_path, _} ->
{archive_entry_rejected, Path, <<"windows path"/utf8>>};
{empty_segment, _} ->
{archive_entry_rejected, Path, <<"empty segment"/utf8>>};
{dot_segment, _} ->
{archive_entry_rejected, Path, <<"dot segment"/utf8>>};
{contains_nul, _} ->
{archive_entry_rejected, Path, <<"nul byte"/utf8>>}
end.
-file("src/packkit/tar.gleam", 521).
-spec link_entry(
parsed_header(),
fun((binary(), binary()) -> {ok, packkit@entry:entry()} |
{error, packkit@entry:entry_error()})
) -> {ok, packkit@entry:entry()} | {error, packkit@error:archive_error()}.
link_entry(Header, Builder) ->
_pipe = Builder(erlang:element(2, Header), erlang:element(9, Header)),
gleam@result:map_error(
_pipe,
fun(_capture) ->
entry_error_to_archive_error(_capture, erlang:element(2, Header))
end
).
-file("src/packkit/tar.gleam", 569).
-spec sum_with_checksum_blanked(bitstring(), integer(), integer()) -> integer().
sum_with_checksum_blanked(Block, Position, Acc) ->
case Block of
<<>> ->
Acc;
<<B, Rest/binary>> ->
Contribution = case (Position >= 148) andalso (Position < 156) of
true ->
16#20;
false ->
B
end,
sum_with_checksum_blanked(Rest, Position + 1, Acc + Contribution);
_ ->
Acc
end.
-file("src/packkit/tar.gleam", 565).
-spec compute_checksum(bitstring()) -> integer().
compute_checksum(Block) ->
sum_with_checksum_blanked(Block, 0, 0).
-file("src/packkit/tar.gleam", 774).
-spec take_split_loop(list(binary()), integer(), list(binary())) -> {list(binary()),
list(binary())}.
take_split_loop(Segments, Remaining, Acc) ->
case {Remaining, Segments} of
{0, _} ->
{lists:reverse(Acc), Segments};
{_, []} ->
{lists:reverse(Acc), []};
{_, [Head | Tail]} ->
take_split_loop(Tail, Remaining - 1, [Head | Acc])
end.
-file("src/packkit/tar.gleam", 767).
-spec take_split(list(binary()), integer()) -> {list(binary()), list(binary())}.
take_split(Segments, Split_at) ->
take_split_loop(Segments, Split_at, []).
-file("src/packkit/tar.gleam", 832).
-spec pow_int_loop(integer(), integer(), integer()) -> integer().
pow_int_loop(Base, Exponent, Acc) ->
case Exponent of
0 ->
Acc;
_ ->
pow_int_loop(Base, Exponent - 1, Acc * Base)
end.
-file("src/packkit/tar.gleam", 828).
-spec pow_int(integer(), integer()) -> integer().
pow_int(Base, Exponent) ->
pow_int_loop(Base, Exponent, 1).
-file("src/packkit/tar.gleam", 853).
-spec version_field() -> bitstring().
version_field() ->
gleam_stdlib:identity(<<"00"/utf8>>).
-file("src/packkit/tar.gleam", 910).
-spec byte_repeat_loop(integer(), integer(), bitstring()) -> bitstring().
byte_repeat_loop(Byte, Count, Acc) ->
case Count of
0 ->
Acc;
_ ->
byte_repeat_loop(Byte, Count - 1, <<Acc/bitstring, Byte>>)
end.
-file("src/packkit/tar.gleam", 906).
-spec byte_repeat(integer(), integer()) -> bitstring().
byte_repeat(Byte, Count) ->
byte_repeat_loop(Byte, Count, <<>>).
-file("src/packkit/tar.gleam", 839).
-spec checksum_blank_field() -> bitstring().
checksum_blank_field() ->
byte_repeat(16#20, 8).
-file("src/packkit/tar.gleam", 857).
-spec uname_field() -> bitstring().
uname_field() ->
byte_repeat(0, 32).
-file("src/packkit/tar.gleam", 861).
-spec gname_field() -> bitstring().
gname_field() ->
byte_repeat(0, 32).
-file("src/packkit/tar.gleam", 873).
-spec tail_padding() -> bitstring().
tail_padding() ->
byte_repeat(0, 12).
-file("src/packkit/tar.gleam", 895).
-spec right_pad(bitstring(), integer(), integer()) -> bitstring().
right_pad(Value, Width, Fill) ->
Size = erlang:byte_size(Value),
case Size >= Width of
true ->
Slice@1 = case gleam_stdlib:bit_array_slice(Value, 0, Width) of
{ok, Slice} -> Slice;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"right_pad"/utf8>>,
line => 899,
value => _assert_fail,
start => 25154,
'end' => 25209,
pattern_start => 25165,
pattern_end => 25174})
end,
Slice@1;
false ->
gleam_stdlib:bit_array_concat(
[Value, byte_repeat(Fill, Width - Size)]
)
end.
-file("src/packkit/tar.gleam", 786).
-spec fit_field(binary(), integer(), binary()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
fit_field(Value, Width, Kind) ->
Bytes = gleam_stdlib:identity(Value),
case erlang:byte_size(Bytes) > Width of
true ->
{error,
{archive_entry_rejected,
Value,
<<<<<<Kind/binary, " longer than "/utf8>>/binary,
(erlang:integer_to_binary(Width))/binary>>/binary,
" bytes"/utf8>>}};
false ->
{ok, right_pad(Bytes, Width, 0)}
end.
-file("src/packkit/tar.gleam", 848).
-spec magic_field() -> bitstring().
magic_field() ->
_pipe = gleam_stdlib:identity(<<"ustar"/utf8>>),
right_pad(_pipe, 6, 0).
-file("src/packkit/tar.gleam", 921).
-spec to_octal_digits_loop(integer(), integer(), bitstring()) -> bitstring().
to_octal_digits_loop(Value, Remaining, Acc) ->
case Remaining of
0 ->
Acc;
_ ->
Digit = Value rem 8,
Next = Value div 8,
Ch = 16#30 + Digit,
to_octal_digits_loop(Next, Remaining - 1, <<Ch, Acc/bitstring>>)
end.
-file("src/packkit/tar.gleam", 917).
-spec to_octal_digits(integer(), integer()) -> bitstring().
to_octal_digits(Value, Digit_width) ->
to_octal_digits_loop(Value, Digit_width, <<>>).
-file("src/packkit/tar.gleam", 802).
-spec octal_field(integer(), integer()) -> bitstring().
octal_field(Value, Width) ->
Digit_width = Width - 1,
Digits = to_octal_digits(Value, Digit_width),
gleam_stdlib:bit_array_concat([Digits, <<0>>]).
-file("src/packkit/tar.gleam", 810).
-spec checked_octal_field(integer(), integer(), binary()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
checked_octal_field(Value, Width, Field) ->
Digit_width = Width - 1,
Max_value = pow_int(8, Digit_width) - 1,
case (Value < 0) orelse (Value > Max_value) of
true ->
{error,
{archive_field_overflow,
<<"tar "/utf8, Field/binary>>,
Value,
Max_value}};
false ->
{ok, octal_field(Value, Width)}
end.
-file("src/packkit/tar.gleam", 843).
-spec checksum_value_field(integer()) -> bitstring().
checksum_value_field(Value) ->
Digits = to_octal_digits(Value, 6),
gleam_stdlib:bit_array_concat([Digits, <<0, 16#20>>]).
-file("src/packkit/tar.gleam", 865).
-spec devmajor_field() -> bitstring().
devmajor_field() ->
octal_field(0, 8).
-file("src/packkit/tar.gleam", 869).
-spec devminor_field() -> bitstring().
devminor_field() ->
octal_field(0, 8).
-file("src/packkit/tar.gleam", 1002).
-spec classify_octal_byte(integer()) -> octal_class().
classify_octal_byte(Byte) ->
case Byte of
0 ->
octal_terminator;
16#20 ->
octal_space;
N when (N >= 16#30) andalso (N =< 16#37) ->
{octal_digit, N - 16#30};
_ ->
octal_invalid
end.
-file("src/packkit/tar.gleam", 966).
-spec parse_octal(bitstring(), integer(), boolean()) -> {ok, integer()} |
{error, packkit@error:archive_error()}.
parse_octal(Bytes, Acc, Any_digit) ->
case Bytes of
<<>> ->
case Any_digit of
true ->
{ok, Acc};
false ->
{ok, 0}
end;
<<B, Rest/binary>> ->
case classify_octal_byte(B) of
{octal_digit, D} ->
parse_octal(Rest, (Acc * 8) + D, true);
octal_space ->
parse_octal(Rest, Acc, Any_digit);
octal_terminator ->
case Any_digit of
true ->
{ok, Acc};
false ->
parse_octal(Rest, Acc, Any_digit)
end;
octal_invalid ->
{error,
{archive_invalid,
<<"invalid octal byte in tar header"/utf8>>}}
end;
_ ->
{ok, Acc}
end.
-file("src/packkit/tar.gleam", 954).
-spec read_octal_field(bitstring(), integer(), integer()) -> {ok, integer()} |
{error, packkit@error:archive_error()}.
read_octal_field(Block, Offset, Width) ->
case gleam_stdlib:bit_array_slice(Block, Offset, Width) of
{error, _} ->
{error, {archive_invalid, <<"header field out of bounds"/utf8>>}};
{ok, Slice} ->
parse_octal(Slice, 0, false)
end.
-file("src/packkit/tar.gleam", 561).
-spec read_checksum_field(bitstring()) -> {ok, integer()} |
{error, packkit@error:archive_error()}.
read_checksum_field(Block) ->
read_octal_field(Block, 148, 8).
-file("src/packkit/tar.gleam", 551).
-spec verify_checksum(bitstring()) -> {ok, nil} |
{error, packkit@error:archive_error()}.
verify_checksum(Block) ->
gleam@result:'try'(
read_checksum_field(Block),
fun(Stored) ->
Computed = compute_checksum(Block),
case Stored =:= Computed of
true ->
{ok, nil};
false ->
{error,
{archive_invalid,
<<"tar header checksum mismatch"/utf8>>}}
end
end
).
-file("src/packkit/tar.gleam", 1011).
-spec read_byte(bitstring(), integer()) -> {ok, integer()} |
{error, packkit@error:archive_error()}.
read_byte(Block, Offset) ->
case gleam_stdlib:bit_array_slice(Block, Offset, 1) of
{ok, <<B>>} ->
{ok, B};
_ ->
{error, {archive_invalid, <<"header byte out of bounds"/utf8>>}}
end.
-file("src/packkit/tar.gleam", 1023).
-spec strip_trailing_nul_loop(bitstring(), integer()) -> bitstring().
strip_trailing_nul_loop(Bytes, Size) ->
case Size of
0 ->
<<>>;
_ ->
case gleam_stdlib:bit_array_slice(Bytes, Size - 1, 1) of
{ok, <<0>>} ->
strip_trailing_nul_loop(Bytes, Size - 1);
_ ->
Slice@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, Size) of
{ok, Slice} -> Slice;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"strip_trailing_nul_loop"/utf8>>,
line => 1030,
value => _assert_fail,
start => 28397,
'end' => 28451,
pattern_start => 28408,
pattern_end => 28417})
end,
Slice@1
end
end.
-file("src/packkit/tar.gleam", 1018).
-spec strip_trailing_nul(bitstring()) -> bitstring().
strip_trailing_nul(Bytes) ->
Size = erlang:byte_size(Bytes),
strip_trailing_nul_loop(Bytes, Size).
-file("src/packkit/tar.gleam", 933).
-spec read_string_field(bitstring(), integer(), integer()) -> {ok, binary()} |
{error, packkit@error:archive_error()}.
read_string_field(Block, Offset, Width) ->
case gleam_stdlib:bit_array_slice(Block, Offset, Width) of
{error, _} ->
{error, {archive_invalid, <<"header field out of bounds"/utf8>>}};
{ok, Slice} ->
Trimmed = strip_trailing_nul(Slice),
case gleam@bit_array:to_string(Trimmed) of
{ok, Value} ->
{ok, Value};
{error, _} ->
{error,
{archive_invalid,
<<"header field contains non-UTF-8 bytes"/utf8>>}}
end
end.
-file("src/packkit/tar.gleam", 1037).
-spec trim_trailing_slash(binary()) -> binary().
trim_trailing_slash(Value) ->
case gleam_stdlib:string_ends_with(Value, <<"/"/utf8>>) of
true ->
gleam@string:drop_end(Value, 1);
false ->
Value
end.
-file("src/packkit/tar.gleam", 514).
-spec directory_entry(parsed_header()) -> {ok, packkit@entry:entry()} |
{error, packkit@error:archive_error()}.
directory_entry(Header) ->
_pipe = packkit@entry:directory_checked(
trim_trailing_slash(erlang:element(2, Header))
),
gleam@result:map_error(
_pipe,
fun(_capture) ->
entry_error_to_archive_error(_capture, erlang:element(2, Header))
end
).
-file("src/packkit/tar.gleam", 1064).
-spec is_zero_block(bitstring()) -> boolean().
is_zero_block(Block) ->
case Block of
<<>> ->
true;
<<0, Rest/binary>> ->
is_zero_block(Rest);
_ ->
false
end.
-file("src/packkit/tar.gleam", 502).
-spec regular_entry(parsed_header(), bitstring()) -> {ok, packkit@entry:entry()} |
{error, packkit@error:archive_error()}.
regular_entry(Header, Bytes) ->
Body@1 = case gleam_stdlib:bit_array_slice(
Bytes,
512,
erlang:element(6, Header)
) of
{ok, Body} ->
Body;
{error, _} ->
<<>>
end,
_pipe = packkit@entry:file_checked(erlang:element(2, Header), Body@1),
gleam@result:map_error(
_pipe,
fun(_capture) ->
entry_error_to_archive_error(_capture, erlang:element(2, Header))
end
).
-file("src/packkit/tar.gleam", 452).
-spec header_to_entry(parsed_header(), bitstring(), packkit@limit:limits()) -> {ok,
packkit@entry:entry()} |
{error, packkit@error:archive_error()}.
header_to_entry(Header, Bytes, Limits) ->
gleam@bool:guard(
erlang:byte_size(erlang:element(2, Header)) > packkit@limit:max_entry_name_bytes(
Limits
),
{error,
{archive_limit_exceeded,
<<"max_entry_name_bytes"/utf8>>,
erlang:byte_size(erlang:element(2, Header))}},
fun() -> gleam@result:'try'(case erlang:element(8, Header) of
16#30 ->
regular_entry(Header, Bytes);
16#00 ->
regular_entry(Header, Bytes);
16#35 ->
directory_entry(Header);
16#32 ->
link_entry(
Header,
fun(P, T) -> packkit@entry:symlink_checked(P, T) end
);
16#31 ->
link_entry(
Header,
fun(P@1, T@1) ->
packkit@entry:hardlink_checked(P@1, T@1)
end
);
Other ->
{error,
{archive_invalid,
<<"unsupported tar typeflag "/utf8,
(erlang:integer_to_binary(Other))/binary>>}}
end, fun(Base_entry) ->
Name_for_depth = trim_trailing_slash(
erlang:element(2, Header)
),
_pipe = case packkit@entry:path_checked(Name_for_depth) of
{ok, Parsed_path} ->
case packkit@entry:depth(Parsed_path) > packkit@limit:max_entry_depth(
Limits
) of
true ->
{error,
{archive_limit_exceeded,
<<"max_entry_depth"/utf8>>,
packkit@entry:depth(Parsed_path)}};
false ->
{ok, nil}
end;
{error, _} ->
{ok, nil}
end,
gleam@result:'try'(
_pipe,
fun(_) ->
{ok,
begin
_pipe@1 = Base_entry,
_pipe@2 = packkit@entry:with_mode(
_pipe@1,
erlang:element(3, Header)
),
_pipe@3 = packkit@entry:with_owner(
_pipe@2,
erlang:element(4, Header),
erlang:element(5, Header)
),
packkit@entry:with_modified_at(
_pipe@3,
erlang:element(7, Header)
)
end}
end
)
end) end
).
-file("src/packkit/tar.gleam", 877).
-spec end_marker() -> bitstring().
end_marker() ->
byte_repeat(0, 512 * 2).
-file("src/packkit/tar.gleam", 888).
-spec round_up_to_block(integer()) -> integer().
round_up_to_block(Size) ->
case case 512 of
0 -> 0;
Gleam@denominator -> Size rem Gleam@denominator
end of
0 ->
Size;
Rem ->
Size + (512 - Rem)
end.
-file("src/packkit/tar.gleam", 881).
-spec pad_to_block(bitstring()) -> bitstring().
pad_to_block(Body) ->
Size = erlang:byte_size(Body),
Padded_size = round_up_to_block(Size),
Padding = Padded_size - Size,
gleam_stdlib:bit_array_concat([Body, byte_repeat(0, Padding)]).
-file("src/packkit/tar.gleam", 1044).
-spec verify_double_zero_terminator(bitstring(), list(packkit@entry:entry())) -> {ok,
list(packkit@entry:entry())} |
{error, packkit@error:archive_error()}.
verify_double_zero_terminator(Bytes, Acc) ->
case gleam_stdlib:bit_array_slice(Bytes, 512, 512) of
{ok, Second} ->
case is_zero_block(Second) of
true ->
{ok, Acc};
false ->
{error,
{archive_invalid,
<<"tar stream ended after a single zero block"/utf8>>}}
end;
{error, _} ->
{error,
{archive_invalid,
<<"tar stream ended after a single zero block"/utf8>>}}
end.
-file("src/packkit/tar.gleam", 742).
-spec search_prefix_split(
list(binary()),
integer(),
integer(),
gleam@option:option({binary(), binary()})
) -> gleam@option:option({binary(), binary()}).
search_prefix_split(Segments, Total, Split_at, Best) ->
case Split_at >= Total of
true ->
Best;
false ->
{Left, Right} = take_split(Segments, Split_at),
Prefix = gleam@string:join(Left, <<"/"/utf8>>),
Rest = gleam@string:join(Right, <<"/"/utf8>>),
Fits = ((erlang:byte_size(Prefix) =< 155) andalso (erlang:byte_size(
Rest
)
=< 100))
andalso (erlang:byte_size(Rest) > 0),
Next_best = case Fits of
true ->
{some, {Prefix, Rest}};
false ->
Best
end,
search_prefix_split(Segments, Total, Split_at + 1, Next_best)
end.
-file("src/packkit/tar.gleam", 732).
-spec find_prefix_split(list(binary())) -> gleam@option:option({binary(),
binary()}).
find_prefix_split(Segments) ->
Total = erlang:length(Segments),
case Total < 2 of
true ->
none;
false ->
search_prefix_split(Segments, Total, 1, none)
end.
-file("src/packkit/tar.gleam", 704).
-spec attempt_prefix_split(binary(), integer()) -> {ok,
{bitstring(), bitstring()}} |
{error, packkit@error:archive_error()}.
attempt_prefix_split(Canonical, Total_bytes) ->
case Total_bytes > ((100 + 155) + 1) of
true ->
{error,
{archive_entry_rejected,
Canonical,
<<"name longer than 256 bytes (USTAR limit)"/utf8>>}};
false ->
Segments = gleam@string:split(Canonical, <<"/"/utf8>>),
case find_prefix_split(Segments) of
{some, {Prefix, Rest}} ->
{ok,
{right_pad(gleam_stdlib:identity(Rest), 100, 0),
right_pad(gleam_stdlib:identity(Prefix), 155, 0)}};
none ->
{error,
{archive_entry_rejected,
Canonical,
<<"no USTAR-compatible prefix/name split"/utf8>>}}
end
end.
-file("src/packkit/tar.gleam", 682).
-spec split_name_field(binary(), packkit@entry:entry_kind()) -> {ok,
{bitstring(), bitstring()}} |
{error, packkit@error:archive_error()}.
split_name_field(Path, Kind) ->
Canonical = case Kind of
directory ->
<<Path/binary, "/"/utf8>>;
_ ->
Path
end,
Canonical_bytes = gleam_stdlib:identity(Canonical),
Size = erlang:byte_size(Canonical_bytes),
case Size =< 100 of
true ->
{ok, {right_pad(Canonical_bytes, 100, 0), right_pad(<<>>, 155, 0)}};
false ->
attempt_prefix_split(Canonical, Size)
end.
-file("src/packkit/tar.gleam", 422).
-spec parse_header(bitstring()) -> {ok, parsed_header()} |
{error, packkit@error:archive_error()}.
parse_header(Block) ->
gleam@result:'try'(
verify_checksum(Block),
fun(_) ->
gleam@result:'try'(
read_string_field(Block, 0, 100),
fun(Raw_name) ->
gleam@result:'try'(
read_octal_field(Block, 100, 8),
fun(Mode) ->
gleam@result:'try'(
read_octal_field(Block, 108, 8),
fun(Uid) ->
gleam@result:'try'(
read_octal_field(Block, 116, 8),
fun(Gid) ->
gleam@result:'try'(
read_octal_field(Block, 124, 12),
fun(Size) ->
gleam@result:'try'(
read_octal_field(
Block,
136,
12
),
fun(Mtime) ->
gleam@result:'try'(
read_byte(
Block,
156
),
fun(Typeflag) ->
gleam@result:'try'(
read_string_field(
Block,
157,
100
),
fun(
Linkname
) ->
gleam@result:'try'(
read_string_field(
Block,
345,
155
),
fun(
Prefix
) ->
Name = case Prefix of
<<""/utf8>> ->
Raw_name;
_ ->
<<<<Prefix/binary,
"/"/utf8>>/binary,
Raw_name/binary>>
end,
{ok,
{parsed_header,
Name,
Mode,
Uid,
Gid,
Size,
Mtime,
Typeflag,
Linkname}}
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/packkit/tar.gleam", 592).
-spec build_header(packkit@entry:entry()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
build_header(Value) ->
Kind = packkit@entry:kind(Value),
Path = packkit@entry:to_string(packkit@entry:path(Value)),
Metadata = packkit@entry:metadata(Value),
gleam@result:'try'(
split_name_field(Path, Kind),
fun(_use0) ->
{Name_field, Prefix_field} = _use0,
Typeflag = case Kind of
file ->
16#30;
directory ->
16#35;
symlink ->
16#32;
hardlink ->
16#31
end,
Size = case Kind of
file ->
erlang:byte_size(packkit@entry:body(Value));
_ ->
0
end,
Linkname = case packkit@entry:link_target(Value) of
{some, Target} ->
Target;
none ->
<<""/utf8>>
end,
gleam@result:'try'(
fit_field(Linkname, 100, <<"linkname"/utf8>>),
fun(Linkname_field) ->
gleam@result:'try'(
checked_octal_field(
packkit@entry:mode(Metadata),
8,
<<"mode"/utf8>>
),
fun(Mode_field) ->
gleam@result:'try'(
checked_octal_field(
packkit@entry:user_id(Metadata),
8,
<<"uid"/utf8>>
),
fun(Uid_field) ->
gleam@result:'try'(
checked_octal_field(
packkit@entry:group_id(Metadata),
8,
<<"gid"/utf8>>
),
fun(Gid_field) ->
gleam@result:'try'(
checked_octal_field(
Size,
12,
<<"size"/utf8>>
),
fun(Size_field) ->
gleam@result:'try'(
checked_octal_field(
packkit@entry:modified_at_unix(
Metadata
),
12,
<<"mtime"/utf8>>
),
fun(Mtime_field) ->
Initial = gleam_stdlib:bit_array_concat(
[Name_field,
Mode_field,
Uid_field,
Gid_field,
Size_field,
Mtime_field,
checksum_blank_field(
),
<<Typeflag>>,
Linkname_field,
magic_field(
),
version_field(
),
uname_field(
),
gname_field(
),
devmajor_field(
),
devminor_field(
),
Prefix_field,
tail_padding(
)]
),
Checksum_value = compute_checksum(
Initial
),
Prefix_bits@1 = case gleam_stdlib:bit_array_slice(
Initial,
0,
148
) of
{ok,
Prefix_bits} -> Prefix_bits;
_assert_fail ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"build_header"/utf8>>,
line => 670,
value => _assert_fail,
start => 19525,
'end' => 19586,
pattern_start => 19536,
pattern_end => 19551}
)
end,
Suffix_bits@1 = case gleam_stdlib:bit_array_slice(
Initial,
156,
erlang:byte_size(
Initial
)
- 156
) of
{ok,
Suffix_bits} -> Suffix_bits;
_assert_fail@1 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"build_header"/utf8>>,
line => 671,
value => _assert_fail@1,
start => 19589,
'end' => 19687,
pattern_start => 19600,
pattern_end => 19615}
)
end,
{ok,
gleam_stdlib:bit_array_concat(
[Prefix_bits@1,
checksum_value_field(
Checksum_value
),
Suffix_bits@1]
)}
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-file("src/packkit/tar.gleam", 583).
-spec encode_entry(packkit@entry:entry()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
encode_entry(Entry_value) ->
gleam@result:'try'(
build_header(Entry_value),
fun(Header) ->
Body = packkit@entry:body(Entry_value),
Body_blocks = pad_to_block(Body),
{ok, gleam_stdlib:bit_array_concat([Header, Body_blocks])}
end
).
-file("src/packkit/tar.gleam", 105).
?DOC(" Encode a logical archive to a USTAR byte stream.\n").
-spec encode(packkit@archive:archive()) -> {ok, bitstring()} |
{error, packkit@error:archive_error()}.
encode(Archive_value) ->
gleam@result:'try'(
reject_comment(Archive_value),
fun(_) -> _pipe = Archive_value,
_pipe@1 = packkit@archive:entries(_pipe),
_pipe@2 = gleam@list:try_map(_pipe@1, fun encode_entry/1),
gleam@result:map(
_pipe@2,
fun(Blocks) ->
_pipe@3 = [gleam_stdlib:bit_array_concat(Blocks),
end_marker()],
gleam_stdlib:bit_array_concat(_pipe@3)
end
) end
).
-file("src/packkit/tar.gleam", 208).
-spec dispatch_typeflag(
bitstring(),
bitstring(),
list(packkit@entry:entry()),
integer(),
packkit@limit:limits(),
pending_override(),
parsed_header()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
dispatch_typeflag(Bytes, Rest, Acc, Count, Limits, Pending, Header) ->
case erlang:element(8, Header) of
16#4C ->
gleam@result:'try'(
read_string_body(
Bytes,
512,
erlang:element(6, Header),
<<"GNU long name"/utf8>>
),
fun(Long_name) ->
decode_loop_with_pending(
Rest,
Acc,
Count,
Limits,
{pending_override,
Long_name,
erlang:element(3, Pending)}
)
end
);
16#4B ->
gleam@result:'try'(
read_string_body(
Bytes,
512,
erlang:element(6, Header),
<<"GNU long linkname"/utf8>>
),
fun(Long_link) ->
decode_loop_with_pending(
Rest,
Acc,
Count,
Limits,
{pending_override,
erlang:element(2, Pending),
Long_link}
)
end
);
16#78 ->
gleam@result:'try'(
read_pax_body(Bytes, 512, erlang:element(6, Header)),
fun(Pax_body) ->
Pending@1 = apply_pax_records(Pending, Pax_body),
decode_loop_with_pending(
Rest,
Acc,
Count,
Limits,
Pending@1
)
end
);
16#67 ->
gleam@result:'try'(
read_pax_body(Bytes, 512, erlang:element(6, Header)),
fun(Pax_body) ->
Pending@1 = apply_pax_records(Pending, Pax_body),
decode_loop_with_pending(
Rest,
Acc,
Count,
Limits,
Pending@1
)
end
);
_ ->
Merged_header = apply_pending(Header, Pending),
gleam@result:'try'(
check_member_limit(Count + 1, Limits),
fun(_) ->
gleam@result:'try'(
header_to_entry(Merged_header, Bytes, Limits),
fun(Entry_value) ->
decode_loop_with_pending(
Rest,
[Entry_value | Acc],
Count + 1,
Limits,
no_pending()
)
end
)
end
)
end.
-file("src/packkit/tar.gleam", 171).
-spec decode_loop_with_pending(
bitstring(),
list(packkit@entry:entry()),
integer(),
packkit@limit:limits(),
pending_override()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
decode_loop_with_pending(Bytes, Acc, Count, Limits, Pending) ->
gleam@bool:guard(
erlang:byte_size(Bytes) < 512,
{error,
{archive_invalid,
<<"tar stream ended before terminator blocks"/utf8>>}},
fun() ->
Header_bits@1 = case gleam_stdlib:bit_array_slice(Bytes, 0, 512) of
{ok, Header_bits} -> Header_bits;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"decode_loop_with_pending"/utf8>>,
line => 184,
value => _assert_fail,
start => 5287,
'end' => 5353,
pattern_start => 5298,
pattern_end => 5313})
end,
gleam@bool:lazy_guard(
is_zero_block(Header_bits@1),
fun() -> verify_double_zero_terminator(Bytes, Acc) end,
fun() ->
gleam@result:'try'(
parse_header(Header_bits@1),
fun(Header) ->
Body_padded = round_up_to_block(
erlang:element(6, Header)
),
Total_advance = 512 + Body_padded,
gleam@bool:guard(
erlang:byte_size(Bytes) < Total_advance,
{error,
{archive_invalid,
<<"tar entry body extends beyond the input"/utf8>>}},
fun() ->
Rest@1 = case gleam_stdlib:bit_array_slice(
Bytes,
Total_advance,
erlang:byte_size(Bytes) - Total_advance
) of
{ok, Rest} -> Rest;
_assert_fail@1 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"packkit/tar"/utf8>>,
function => <<"decode_loop_with_pending"/utf8>>,
line => 199,
value => _assert_fail@1,
start => 5954,
'end' => 6086,
pattern_start => 5965,
pattern_end => 5973}
)
end,
dispatch_typeflag(
Bytes,
Rest@1,
Acc,
Count,
Limits,
Pending,
Header
)
end
)
end
)
end
)
end
).
-file("src/packkit/tar.gleam", 162).
-spec decode_loop(
bitstring(),
list(packkit@entry:entry()),
integer(),
packkit@limit:limits()
) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}.
decode_loop(Bytes, Acc, Count, Limits) ->
decode_loop_with_pending(Bytes, Acc, Count, Limits, no_pending()).
-file("src/packkit/tar.gleam", 137).
?DOC(
" Decode a USTAR byte stream into a logical archive using the supplied\n"
" resource limits.\n"
).
-spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok,
packkit@archive:archive()} |
{error, packkit@error:archive_error()}.
decode_with_limits(Bytes, Limits) ->
gleam@bool:guard(
erlang:byte_size(Bytes) > packkit@limit:max_input_bytes(Limits),
{error,
{archive_limit_exceeded,
<<"max_input_bytes"/utf8>>,
erlang:byte_size(Bytes)}},
fun() -> _pipe = decode_loop(Bytes, [], 0, Limits),
_pipe@1 = gleam@result:map(_pipe, fun lists:reverse/1),
gleam@result:map(
_pipe@1,
fun(_capture) ->
packkit@archive:from_entries(format(), _capture)
end
) end
).
-file("src/packkit/tar.gleam", 129).
?DOC(
" Decode a USTAR byte stream into a logical archive using the default\n"
" resource limits.\n"
).
-spec decode(bitstring()) -> {ok, packkit@archive:archive()} |
{error, packkit@error:archive_error()}.
decode(Bytes) ->
decode_with_limits(Bytes, packkit@limit:default()).