-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 \" =\\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 \" =\\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 => <>, 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; <> -> 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, <>) 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 => <>, 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, <<<<<>/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, <>) 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; <> -> 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, <>} -> {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 => <>, 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 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; _ -> <<<>/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( ), <>, 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 => <>, 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 => <>, 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 => <>, 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 => <>, 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()).