-module(packkit@zip). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/packkit/zip.gleam"). -export([format/0, new/0, store/0, deflate/1, bzip2/0, zstd/0, xz/0, lzma/0, name/1, inner_codec/1, encode_with_method/2, encode/1, decode/1, decode_with_limits/2, decode_with_password/2, decode_with_password_and_limits/3]). -export_type([method/0, encode_fold/0, eocd_record/0, pkware_keys/0, aex_params/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( " ZIP archive encoder and decoder.\n" "\n" " This module implements the PKZIP local-file-header / central\n" " directory layout for the \"stored\" (uncompressed) and \"deflate\"\n" " methods, plus the Zip64 extensions (APPNOTE.TXT §4.4) needed to\n" " carry archives with > 65535 entries, central-directory regions\n" " > 4 GiB, individual entries > 4 GiB, or local-header offsets\n" " > 4 GiB. ZIP is modelled as an archive family, not a recipe:\n" " per-entry compression is selected through `Method` values rather\n" " than through `packkit/recipe`.\n" "\n" " On the decoder side: when the standard EOCD record has any\n" " sentinel field (`0xFFFF` for the 16-bit slots, `0xFFFFFFFF` for\n" " the 32-bit slots), the decoder follows the Zip64 EOCD locator at\n" " `eocd_offset - 20` to the Zip64 EOCD record and reads the real\n" " 64-bit values from there. Per-entry Zip64 extra fields\n" " (`header_id = 0x0001`) are parsed in both the central-directory\n" " entry and the local file header so that compressed / uncompressed\n" " sizes and local-header offsets above the 4 GiB boundary decode\n" " correctly.\n" "\n" " On the encoder side: each entry transparently switches to the\n" " Zip64 extra field when any of its `uncompressed_size`,\n" " `compressed_size`, or `local_header_offset` would not fit in 32\n" " bits. Archives whose total entry count or central-directory\n" " region overflow the legacy 16-/32-bit EOCD slots also emit a\n" " Zip64 EOCD record + locator so the resulting bytes round-trip\n" " through any conforming Zip64 reader.\n" ). -opaque method() :: {method, binary(), gleam@option:option(packkit@codec:codec())}. -type encode_fold() :: {encode_fold, integer(), list(bitstring()), list(bitstring()), gleam@option:option(packkit@error:archive_error())}. -type eocd_record() :: {eocd_record, integer(), integer(), integer(), gleam@option:option(binary())}. -type pkware_keys() :: {pkware_keys, integer(), integer(), integer()}. -type aex_params() :: {aex_params, integer(), integer(), integer()}. -file("src/packkit/zip.gleam", 174). ?DOC(" ZIP archive format marker.\n"). -spec format() -> packkit@archive:archive_format(). format() -> packkit@archive:zip(). -file("src/packkit/zip.gleam", 179). ?DOC(" Create an empty logical ZIP archive value.\n"). -spec new() -> packkit@archive:archive(). new() -> packkit@archive:new(format()). -file("src/packkit/zip.gleam", 184). ?DOC(" Stored (uncompressed) ZIP member method.\n"). -spec store() -> method(). store() -> {method, <<"store"/utf8>>, none}. -file("src/packkit/zip.gleam", 189). ?DOC(" Deflate-compressed ZIP member method.\n"). -spec deflate(packkit@level:level()) -> method(). deflate(Level) -> {method, <<"deflate"/utf8>>, {some, begin _pipe = packkit@codec:deflate(), packkit@codec:with_level(_pipe, Level) end}}. -file("src/packkit/zip.gleam", 197). ?DOC(" Bzip2-compressed ZIP member method (PKZIP method 12).\n"). -spec bzip2() -> method(). bzip2() -> {method, <<"bzip2"/utf8>>, {some, packkit@codec:bzip2()}}. -file("src/packkit/zip.gleam", 202). ?DOC(" Zstd-compressed ZIP member method (PKZIP method 93).\n"). -spec zstd() -> method(). zstd() -> {method, <<"zstd"/utf8>>, {some, packkit@codec:zstd()}}. -file("src/packkit/zip.gleam", 207). ?DOC(" xz-compressed ZIP member method (PKZIP method 95).\n"). -spec xz() -> method(). xz() -> {method, <<"xz"/utf8>>, {some, packkit@codec:xz()}}. -file("src/packkit/zip.gleam", 217). ?DOC( " PKWARE LZMA ZIP member method (PKZIP method 14). Wraps a raw\n" " LZMA1 range-coded stream in the 4-byte SDK preamble + 5-byte\n" " property block (`lc=3 / lp=0 / pb=2`, dict size 64 KiB). The\n" " encoder emits the stream with general-purpose flag bit 1 set so\n" " the decoder relies on the central-directory uncompressed size\n" " instead of an in-stream EOS marker.\n" ). -spec lzma() -> method(). lzma() -> {method, <<"lzma"/utf8>>, none}. -file("src/packkit/zip.gleam", 224). ?DOC(" Stable method name.\n"). -spec name(method()) -> binary(). name(Method) -> erlang:element(2, Method). -file("src/packkit/zip.gleam", 229). ?DOC(" Optional inner codec corresponding to the method.\n"). -spec inner_codec(method()) -> gleam@option:option(packkit@codec:codec()). inner_codec(Method) -> erlang:element(3, Method). -file("src/packkit/zip.gleam", 370). ?DOC( " Run the deflate encoder honoring the requested level on the inner\n" " codec attached to the method. Level 0 → stored deflate blocks;\n" " the implicit default level → fixed-Huffman LZ77; any other level\n" " is rejected with a typed `CodecOptionUnsupported`. Previously this\n" " step silently fell back to the default encoder regardless of the\n" " caller's level request.\n" ). -spec deflate_with_level( bitstring(), gleam@option:option(packkit@codec:codec()) ) -> {ok, bitstring()} | {error, packkit@error:codec_error()}. deflate_with_level(Bytes, Inner) -> Level_value = case Inner of none -> none; {some, C} -> case packkit@codec:level(C) of {some, L} -> {some, packkit@level:value(L)}; none -> none end end, case Level_value of none -> packkit@deflate:encode(Bytes); {some, 0} -> packkit@deflate:encode_stored_only(Bytes); {some, N} -> case N =:= packkit@level:value(packkit@level:default()) of true -> packkit@deflate:encode(Bytes); false -> {error, {codec_option_unsupported, <<"level"/utf8>>, <<"zip-deflate"/utf8>>}} end end. -file("src/packkit/zip.gleam", 883). -spec civil_from_days(integer()) -> {integer(), integer(), integer()}. civil_from_days(Z) -> Days_shifted = Z + 719468, Era = case Days_shifted >= 0 of true -> Days_shifted div 146097; false -> (Days_shifted - 146096) div 146097 end, Day_of_era = Days_shifted - (Era * 146097), Year_of_era = (((Day_of_era - (Day_of_era div 1460)) + (Day_of_era div 36524)) - (Day_of_era div 146096)) div 365, March_year = Year_of_era + (Era * 400), Day_of_year = Day_of_era - (((365 * Year_of_era) + (Year_of_era div 4)) - (Year_of_era div 100)), Month_prime = ((5 * Day_of_year) + 2) div 153, Day = (Day_of_year - (((153 * Month_prime) + 2) div 5)) + 1, Month = case Month_prime < 10 of true -> Month_prime + 3; false -> Month_prime - 9 end, Year = case Month =< 2 of true -> March_year + 1; false -> March_year end, {Year, Month, Day}. -file("src/packkit/zip.gleam", 921). -spec days_from_civil(integer(), integer(), integer()) -> integer(). days_from_civil(Year, Month, Day) -> March_year = case Month =< 2 of true -> Year - 1; false -> Year end, Era = case March_year >= 0 of true -> March_year div 400; false -> (March_year - 399) div 400 end, Year_of_era = March_year - (Era * 400), Month_prime = case Month > 2 of true -> Month - 3; false -> Month + 9 end, Day_of_year = ((((153 * Month_prime) + 2) div 5) + Day) - 1, Day_of_era = (((Year_of_era * 365) + (Year_of_era div 4)) - (Year_of_era div 100)) + Day_of_year, ((Era * 146097) + Day_of_era) - 719468. -file("src/packkit/zip.gleam", 1065). -spec option_try( {ok, UOS} | {error, any()}, fun((UOS) -> gleam@option:option(UOW)) ) -> gleam@option:option(UOW). option_try(Source, Continuation) -> case Source of {ok, Value} -> Continuation(Value); {error, _} -> none end. -file("src/packkit/zip.gleam", 1075). -spec option_try_pair( gleam@option:option(UOZ), fun((UOZ) -> gleam@option:option(UPB)) ) -> gleam@option:option(UPB). option_try_pair(Source, Continuation) -> case Source of {some, Value} -> Continuation(Value); none -> none end. -file("src/packkit/zip.gleam", 1103). -spec decode_unix_uid_gid_value(bitstring(), integer()) -> gleam@option:option({integer(), bitstring()}). decode_unix_uid_gid_value(Bytes, Width) -> case {Width, Bytes} of {1, <>} -> {some, {Value, Rest}}; {2, <>} -> {some, {Value@1, Rest@1}}; {4, <>} -> {some, {Value@2, Rest@2}}; {8, <>} -> {some, {Value@3, Rest@3}}; {_, _} -> none end. -file("src/packkit/zip.gleam", 1053). -spec parse_gid_value(bitstring(), integer(), integer()) -> gleam@option:option({integer(), integer()}). parse_gid_value(Gid_rest, Gid_size, Uid) -> option_try_pair( decode_unix_uid_gid_value(Gid_rest, Gid_size), fun(_use0) -> {Gid, _} = _use0, {some, {Uid, Gid}} end ). -file("src/packkit/zip.gleam", 1046). -spec parse_gid_field(bitstring(), integer()) -> gleam@option:option({integer(), integer()}). parse_gid_field(After_uid, Uid) -> case After_uid of <> -> parse_gid_value(Gid_rest, Gid_size, Uid); _ -> none end. -file("src/packkit/zip.gleam", 1038). -spec parse_uid_then_gid(bitstring(), integer()) -> gleam@option:option({integer(), integer()}). parse_uid_then_gid(Rest, Uid_size) -> option_try_pair( decode_unix_uid_gid_value(Rest, Uid_size), fun(_use0) -> {Uid, After_uid} = _use0, parse_gid_field(After_uid, Uid) end ). -file("src/packkit/zip.gleam", 1031). -spec parse_unix_uid_gid_new_body(bitstring()) -> gleam@option:option({integer(), integer()}). parse_unix_uid_gid_new_body(Body) -> case Body of <<_, Uid_size, Rest/binary>> -> parse_uid_then_gid(Rest, Uid_size); _ -> none end. -file("src/packkit/zip.gleam", 1145). -spec ensure_trailing_slash(binary()) -> binary(). ensure_trailing_slash(Value) -> case gleam_stdlib:string_ends_with(Value, <<"/"/utf8>>) of true -> Value; false -> <> end. -file("src/packkit/zip.gleam", 1733). -spec pkware_initial_keys() -> pkware_keys(). pkware_initial_keys() -> {pkware_keys, 16#12345678, 16#23456789, 16#34567890}. -file("src/packkit/zip.gleam", 1773). -spec pkware_crc32_fold(integer(), integer()) -> integer(). pkware_crc32_fold(Value, Rounds) -> case Rounds of 0 -> Value; _ -> Next = case erlang:'band'(Value, 1) of 1 -> erlang:'bxor'(erlang:'bsr'(Value, 1), 16#EDB88320); _ -> erlang:'bsr'(Value, 1) end, pkware_crc32_fold(Next, Rounds - 1) end. -file("src/packkit/zip.gleam", 1767). -spec pkware_crc32_byte(integer(), integer()) -> integer(). pkware_crc32_byte(Crc, Byte) -> Idx = erlang:'band'(erlang:'bxor'(Crc, Byte), 16#FF), Folded = pkware_crc32_fold(Idx, 8), erlang:'bxor'(erlang:'bsr'(Crc, 8), Folded). -file("src/packkit/zip.gleam", 1751). -spec pkware_update_keys(pkware_keys(), integer()) -> pkware_keys(). pkware_update_keys(Keys, Byte) -> New_key0 = pkware_crc32_byte(erlang:element(2, Keys), Byte), Added = erlang:'band'(New_key0, 16#FF) + erlang:element(3, Keys), Mixed = (Added * 134775813) + 1, New_key1 = erlang:'band'(Mixed, 16#FFFFFFFF), Top = erlang:'band'(erlang:'bsr'(New_key1, 24), 16#FF), New_key2 = pkware_crc32_byte(erlang:element(4, Keys), Top), {pkware_keys, New_key0, New_key1, New_key2}. -file("src/packkit/zip.gleam", 1744). -spec pkware_seed_loop(pkware_keys(), bitstring()) -> pkware_keys(). pkware_seed_loop(Keys, Password) -> case Password of <> -> pkware_seed_loop(pkware_update_keys(Keys, B), Rest); _ -> Keys end. -file("src/packkit/zip.gleam", 1740). -spec pkware_keys_from_password(binary()) -> pkware_keys(). pkware_keys_from_password(Password) -> pkware_seed_loop(pkware_initial_keys(), gleam_stdlib:identity(Password)). -file("src/packkit/zip.gleam", 1790). -spec pkware_decrypt_byte(pkware_keys(), integer()) -> {integer(), pkware_keys()}. pkware_decrypt_byte(Keys, Cipher) -> Temp = erlang:'bor'(erlang:element(4, Keys), 2), Prod = Temp * erlang:'bxor'(Temp, 1), Mask = erlang:'band'(erlang:'bsr'(Prod, 8), 16#FF), Plain = erlang:'bxor'(Cipher, Mask), Next_keys = pkware_update_keys(Keys, Plain), {Plain, Next_keys}. -file("src/packkit/zip.gleam", 1800). -spec pkware_decrypt_bytes(pkware_keys(), bitstring(), bitstring()) -> {bitstring(), pkware_keys()}. pkware_decrypt_bytes(Keys, Cipher, Acc) -> case Cipher of <> -> {Plain, Keys@1} = pkware_decrypt_byte(Keys, B), pkware_decrypt_bytes(Keys@1, Rest, <>); _ -> {Acc, Keys} end. -file("src/packkit/zip.gleam", 1869). -spec split_enc_header(bitstring(), integer()) -> {ok, {bitstring(), bitstring()}} | {error, packkit@error:archive_error()}. split_enc_header(Raw, Total_size) -> case {gleam_stdlib:bit_array_slice(Raw, 0, 12), gleam_stdlib:bit_array_slice(Raw, 12, Total_size - 12)} of {{ok, H}, {ok, P}} -> {ok, {H, P}}; {{error, _}, _} -> {error, {archive_invalid, <<"ZIP encryption header truncated (need ≥ 12 bytes)"/utf8>>}}; {_, {error, _}} -> {error, {archive_invalid, <<"ZIP encrypted entry payload truncated"/utf8>>}} end. -file("src/packkit/zip.gleam", 1890). -spec pkware_last_byte_loop(bitstring(), integer()) -> integer(). pkware_last_byte_loop(Bytes, Last) -> case Bytes of <> -> pkware_last_byte_loop(Rest, B); _ -> Last end. -file("src/packkit/zip.gleam", 1886). -spec pkware_last_byte(bitstring()) -> integer(). pkware_last_byte(Bytes) -> pkware_last_byte_loop(Bytes, 0). -file("src/packkit/zip.gleam", 2035). -spec aex_key_length(integer(), binary()) -> {ok, integer()} | {error, packkit@error:archive_error()}. aex_key_length(Strength, Name) -> case Strength of 16#01 -> {ok, 16}; 16#02 -> {ok, 24}; 16#03 -> {ok, 32}; _ -> {error, {archive_invalid, <<<<<<<<"ZIP AE-x unknown AES strength byte "/utf8, (erlang:integer_to_binary(Strength))/binary>>/binary, " for \""/utf8>>/binary, Name/binary>>/binary, "\""/utf8>>}} end. -file("src/packkit/zip.gleam", 2103). -spec xor_bit_arrays_zip(bitstring(), bitstring(), bitstring()) -> bitstring(). xor_bit_arrays_zip(Left, Right, Acc) -> case {Left, Right} of {<>, <>} -> Mixed = erlang:'bxor'(L, R), xor_bit_arrays_zip( L_rest, R_rest, gleam_stdlib:bit_array_concat([Acc, <>]) ); {_, _} -> Acc end. -file("src/packkit/zip.gleam", 2166). -spec parse_pkware_lzma_header(bitstring()) -> {ok, {integer(), bitstring(), bitstring()}} | {error, packkit@error:codec_error()}. parse_pkware_lzma_header(Compressed) -> case Compressed of <<_, _, Prop_size:16/little-unsigned, Rest/binary>> -> Rest_size = erlang:byte_size(Rest), gleam@bool:guard( Rest_size < Prop_size, {error, {codec_invalid_data, <<"PKWARE LZMA wrapper truncated property block"/utf8>>}}, fun() -> Prop_slice = gleam_stdlib:bit_array_slice( Rest, 0, Prop_size ), Payload_slice = gleam_stdlib:bit_array_slice( Rest, Prop_size, Rest_size - Prop_size ), case {Prop_slice, Payload_slice} of {{ok, Prop_block}, {ok, Payload}} -> {ok, {Prop_size, Prop_block, Payload}}; {_, _} -> {error, {codec_invalid_data, <<"PKWARE LZMA wrapper slice out of range"/utf8>>}} end end ); _ -> {error, {codec_invalid_data, <<"PKWARE LZMA wrapper missing header"/utf8>>}} end. -file("src/packkit/zip.gleam", 2196). -spec parse_pkware_lzma_props(bitstring()) -> {ok, packkit@internal@lzma:properties()} | {error, packkit@error:codec_error()}. parse_pkware_lzma_props(Prop_block) -> case Prop_block of <> -> packkit@internal@lzma:properties_of_byte(Prop_byte); _ -> {error, {codec_invalid_data, <<"PKWARE LZMA wrapper property block has wrong length"/utf8>>}} end. -file("src/packkit/zip.gleam", 2135). ?DOC( " Decode a ZIP method 14 PKWARE LZMA payload to its plain bytes.\n" "\n" " PKZIP APPNOTE.TXT §5.8 lays the wrapper out as:\n" " +0 1 byte LZMA SDK major version\n" " +1 1 byte LZMA SDK minor version\n" " +2 2 bytes Size of the LZMA property block (LE, normally 5)\n" " +4 N bytes LZMA1 property block (1 properties byte + 4 little-\n" " endian dictionary-size bytes when N == 5)\n" " +4+N Raw LZMA1 range-coded payload (the byte stream\n" " expected by `packkit/internal/lzma.new`)\n" "\n" " We forward the property byte to `lzma.properties_of_byte`, the\n" " remaining range-coded bytes to `lzma.new`, and ask the decoder\n" " for exactly the central-directory uncompressed size — the EOS\n" " marker (when present in payload-bit-1 = 0 streams) terminates\n" " inside the range coder and the size-limited loop covers the\n" " payload-bit-1 = 1 case. The dictionary-size field is informational\n" " for the wrapper; the LZMA decoder allocates its window lazily.\n" ). -spec decode_pkware_lzma(bitstring(), integer(), packkit@limit:limits()) -> {ok, bitstring()} | {error, packkit@error:codec_error()}. decode_pkware_lzma(Compressed, Uncomp_size, Limits) -> gleam@result:'try'( parse_pkware_lzma_header(Compressed), fun(_use0) -> {Prop_size, Prop_block, Payload} = _use0, gleam@bool:guard( Prop_size /= 5, {error, {codec_invalid_data, <<"PKWARE LZMA wrapper property size must be 5"/utf8>>}}, fun() -> gleam@result:'try'( parse_pkware_lzma_props(Prop_block), fun(Props) -> gleam@bool:guard( Uncomp_size > packkit@limit:max_output_bytes( Limits ), {error, {codec_limit_exceeded, <<"max_output_bytes"/utf8>>, Uncomp_size}}, fun() -> gleam@result:'try'( packkit@internal@lzma:new( Payload, Props, packkit@limit:max_output_bytes( Limits ) ), fun(Decoder) -> gleam@result:'try'( packkit@internal@lzma:decode_into( Decoder, Uncomp_size ), fun(_use0@1) -> {Decoded, _} = _use0@1, {ok, Decoded} end ) end ) end ) end ) end ) end ). -file("src/packkit/zip.gleam", 2216). ?DOC( " Build a PKWARE method-14 payload by gluing the 4-byte SDK preamble\n" " + 5-byte property block in front of an LZMA1 raw range-coded\n" " stream produced by `packkit/internal/lzma.encode_literal_only`.\n" " The general-purpose flag bit 1 (set in the entry's local + central\n" " headers) tells the reader to use the uncompressed size from the\n" " central directory instead of looking for an in-stream EOS marker —\n" " which our literal-only encoder never emits.\n" ). -spec encode_pkware_lzma(bitstring()) -> bitstring(). encode_pkware_lzma(Plain) -> Props = {properties, 3, 0, 2}, Stream = packkit@internal@lzma:encode_with_lz77(Plain, Props), Prop_byte = packkit@internal@lzma:properties_to_byte(Props), Dict_size_bytes = <<16#10000:32/little>>, Preamble = <<16#14, 16#00, 16#05, 16#00>>, gleam_stdlib:bit_array_concat( [Preamble, <>, Dict_size_bytes, Stream] ). -file("src/packkit/zip.gleam", 2236). -spec strip_trailing_slash(binary()) -> binary(). strip_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/zip.gleam", 2243). -spec path_depth(binary()) -> integer(). path_depth(Name) -> case Name of <<""/utf8>> -> 0; _ -> erlang:length(gleam@string:split(Name, <<"/"/utf8>>)) end. -file("src/packkit/zip.gleam", 2250). -spec codec_to_archive_error(packkit@error:codec_error(), binary()) -> packkit@error:archive_error(). codec_to_archive_error(Err, Path) -> case Err of {codec_invalid_data, Message} -> {archive_entry_rejected, Path, <<"deflate decode failed: "/utf8, Message/binary>>}; {codec_limit_exceeded, Limit, Actual} -> {archive_limit_exceeded, Limit, Actual}; {codec_dictionary_required, _} -> {archive_entry_rejected, Path, <<"deflate decode requires preset dictionary (not supported)"/utf8>>}; {codec_dictionary_mismatch, _} -> {archive_entry_rejected, Path, <<"deflate decode preset dictionary mismatch"/utf8>>}; {codec_option_unsupported, Option, Codec_name} -> {archive_entry_rejected, Path, <<<<<<"codec "/utf8, Codec_name/binary>>/binary, " does not support the requested option: "/utf8>>/binary, Option/binary>>}; {codec_not_implemented, Feature} -> {archive_not_implemented, Feature} end. -file("src/packkit/zip.gleam", 2285). -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/zip.gleam", 2307). -spec slice_or_error(bitstring(), integer(), integer()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. slice_or_error(Bytes, Offset, Width) -> case gleam_stdlib:bit_array_slice(Bytes, Offset, Width) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {archive_invalid, <<"ZIP slice out of bounds"/utf8>>}} end. -file("src/packkit/zip.gleam", 1821). -spec resolve_pkware_decryption( bitstring(), integer(), integer(), boolean(), integer(), gleam@option:option(binary()), binary() ) -> {ok, {integer(), integer(), bitstring()}} | {error, packkit@error:archive_error()}. resolve_pkware_decryption( Full, Data_offset, Total_size, Encrypted, Expected_crc, Password, Name ) -> case {Encrypted, Password} of {false, _} -> {ok, {Data_offset, Total_size, Full}}; {true, none} -> {error, {archive_not_implemented, <<<<"encrypted ZIP entry \""/utf8, Name/binary>>/binary, "\" (use decode_with_password)"/utf8>>}}; {true, {some, Pwd}} -> gleam@result:'try'( slice_or_error(Full, Data_offset, Total_size), fun(Raw) -> gleam@result:'try'( split_enc_header(Raw, Total_size), fun(_use0) -> {Header_cipher, Payload_cipher} = _use0, Keys = pkware_keys_from_password(Pwd), {Header_plain, Keys@1} = pkware_decrypt_bytes( Keys, Header_cipher, <<>> ), Check = pkware_last_byte(Header_plain), Expected_check = erlang:'band'( erlang:'bsr'(Expected_crc, 24), 16#FF ), gleam@bool:guard( Check /= Expected_check, {error, {archive_invalid, <<<<"ZIP wrong password or corrupt encryption header for \""/utf8, Name/binary>>/binary, "\""/utf8>>}}, fun() -> {Payload_plain, _} = pkware_decrypt_bytes( Keys@1, Payload_cipher, <<>> ), {ok, {0, Total_size - 12, Payload_plain}} end ) end ) end ) end. -file("src/packkit/zip.gleam", 2068). -spec aes_ctr_loop( packkit@internal@aes:expanded_key(), bitstring(), integer(), bitstring(), binary() ) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. aes_ctr_loop(Key, Ciphertext, Counter, Acc, Name) -> case Ciphertext of <<>> -> {ok, Acc}; _ -> Counter_block = <>, gleam@result:'try'( case packkit@internal@aes:encrypt_block(Key, Counter_block) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {archive_invalid, <<<<"ZIP AE-x CTR block encrypt failed for \""/utf8, Name/binary>>/binary, "\""/utf8>>}} end, fun(Keystream) -> Chunk_size = case erlang:byte_size(Ciphertext) of N when N < 16 -> N; _ -> 16 end, gleam@result:'try'( slice_or_error(Ciphertext, 0, Chunk_size), fun(Chunk) -> gleam@result:'try'( slice_or_error(Keystream, 0, Chunk_size), fun(Keystream_chunk) -> Xored = xor_bit_arrays_zip( Chunk, Keystream_chunk, <<>> ), gleam@result:'try'( slice_or_error( Ciphertext, Chunk_size, erlang:byte_size(Ciphertext) - Chunk_size ), fun(Rest) -> aes_ctr_loop( Key, Rest, Counter + 1, gleam_stdlib:bit_array_concat( [Acc, Xored] ), Name ) end ) end ) end ) end ) end. -file("src/packkit/zip.gleam", 2060). -spec aes_ctr_xor(packkit@internal@aes:expanded_key(), bitstring(), binary()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. aes_ctr_xor(Key, Ciphertext, Name) -> aes_ctr_loop(Key, Ciphertext, 1, <<>>, Name). -file("src/packkit/zip.gleam", 2318). -spec bytes_to_string(bitstring()) -> {ok, binary()} | {error, packkit@error:archive_error()}. bytes_to_string(Bytes) -> case gleam@bit_array:to_string(Bytes) of {ok, Value} -> {ok, Value}; {error, _} -> {error, {archive_invalid, <<"non-UTF-8 ZIP name (set EFS flag)"/utf8>>}} end. -file("src/packkit/zip.gleam", 2333). -spec name_needs_utf8_flag(bitstring()) -> boolean(). name_needs_utf8_flag(Name_bytes) -> case Name_bytes of <> -> case B >= 16#80 of true -> true; false -> name_needs_utf8_flag(Rest) end; _ -> false end. -file("src/packkit/zip.gleam", 2344). -spec le16(integer()) -> bitstring(). le16(Value) -> <>. -file("src/packkit/zip.gleam", 2348). -spec le32(integer()) -> bitstring(). le32(Value) -> <>. -file("src/packkit/zip.gleam", 2352). -spec read_le16_at(bitstring(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. read_le16_at(Bytes, Offset) -> case gleam_stdlib:bit_array_slice(Bytes, Offset, 2) of {ok, <>} -> {ok, Value}; _ -> {error, {archive_invalid, <<"short read for 16-bit value"/utf8>>}} end. -file("src/packkit/zip.gleam", 2359). -spec read_le32_at(bitstring(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. read_le32_at(Bytes, Offset) -> case gleam_stdlib:bit_array_slice(Bytes, Offset, 4) of {ok, <>} -> {ok, Value}; _ -> {error, {archive_invalid, <<"short read for 32-bit value"/utf8>>}} end. -file("src/packkit/zip.gleam", 2366). -spec read_le32(bitstring()) -> {ok, integer()} | {error, packkit@error:archive_error()}. read_le32(Bytes) -> case Bytes of <> -> {ok, Value}; _ -> {error, {archive_invalid, <<"short read for 32-bit value"/utf8>>}} end. -file("src/packkit/zip.gleam", 2373). -spec read_le64_at(bitstring(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. read_le64_at(Bytes, Offset) -> case gleam_stdlib:bit_array_slice(Bytes, Offset, 8) of {ok, <>} -> {ok, Value}; _ -> {error, {archive_invalid, <<"short read for 64-bit value"/utf8>>}} end. -file("src/packkit/zip.gleam", 2380). -spec le64(integer()) -> bitstring(). le64(Value) -> <>. -file("src/packkit/zip.gleam", 2455). -spec maybe_read_le64(bitstring(), boolean()) -> {ok, {gleam@option:option(integer()), bitstring()}} | {error, packkit@error:archive_error()}. maybe_read_le64(Payload, Needed) -> case Needed of false -> {ok, {none, Payload}}; true -> case Payload of <> -> {ok, {{some, Value}, Rest}}; _ -> {error, {archive_invalid, <<"Zip64 extra field truncated"/utf8>>}} end end. -file("src/packkit/zip.gleam", 2437). -spec decode_zip64_extra_payload(bitstring(), boolean(), boolean(), boolean()) -> {ok, {gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer())}} | {error, packkit@error:archive_error()}. decode_zip64_extra_payload( Payload, Uncomp_at_sentinel, Comp_at_sentinel, Offset_at_sentinel ) -> gleam@result:'try'( maybe_read_le64(Payload, Uncomp_at_sentinel), fun(_use0) -> {Uncomp, Payload@1} = _use0, gleam@result:'try'( maybe_read_le64(Payload@1, Comp_at_sentinel), fun(_use0@1) -> {Comp, Payload@2} = _use0@1, gleam@result:'try'( maybe_read_le64(Payload@2, Offset_at_sentinel), fun(_use0@2) -> {Offset, _} = _use0@2, {ok, {Uncomp, Comp, Offset}} end ) end ) end ). -file("src/packkit/zip.gleam", 1175). -spec scan_eocd(bitstring(), integer(), integer()) -> {ok, integer()} | {error, packkit@error:archive_error()}. scan_eocd(Bytes, Position, Floor) -> case Position < Floor of true -> {error, {archive_invalid, <<"missing ZIP EOCD signature"/utf8>>}}; false -> case gleam_stdlib:bit_array_slice(Bytes, Position, 4) of {ok, Slice} -> case read_le32(Slice) of {ok, Value} when Value =:= 16#06054b50 -> {ok, Position}; _ -> scan_eocd(Bytes, Position - 1, Floor) end; {error, _} -> scan_eocd(Bytes, Position - 1, Floor) end end. -file("src/packkit/zip.gleam", 1161). -spec locate_eocd(bitstring()) -> {ok, integer()} | {error, packkit@error:archive_error()}. locate_eocd(Bytes) -> Size = erlang:byte_size(Bytes), case Size < 22 of true -> {error, {archive_invalid, <<"ZIP stream too short"/utf8>>}}; false -> Start = case ((Size - 22) - 65535) < 0 of true -> 0; false -> (Size - 22) - 65535 end, scan_eocd(Bytes, Size - 22, Start) end. -file("src/packkit/zip.gleam", 1291). -spec read_zip64_eocd_record(bitstring(), integer()) -> {ok, {integer(), integer(), integer()}} | {error, packkit@error:archive_error()}. read_zip64_eocd_record(Bytes, Offset) -> gleam@result:'try'( read_le32_at(Bytes, Offset), fun(Signature) -> gleam@bool:guard( Signature /= 16#06064b50, {error, {archive_invalid, <<"Zip64 EOCD signature missing at locator-pointed offset"/utf8>>}}, fun() -> gleam@result:'try'( read_le64_at(Bytes, Offset + 32), fun(Total_entries) -> gleam@result:'try'( read_le64_at(Bytes, Offset + 40), fun(Central_size) -> gleam@result:'try'( read_le64_at(Bytes, Offset + 48), fun(Central_offset) -> {ok, {Total_entries, Central_size, Central_offset}} end ) end ) end ) end ) end ). -file("src/packkit/zip.gleam", 1264). ?DOC( " Locate and parse the Zip64 EOCD record. The locator sits in the\n" " 20 bytes immediately preceding the standard EOCD signature; the\n" " 8-byte little-endian field at offset 8 of the locator points to\n" " the Zip64 EOCD record itself.\n" ). -spec read_zip64_eocd_from_locator(bitstring(), integer()) -> {ok, {integer(), integer(), integer()}} | {error, packkit@error:archive_error()}. read_zip64_eocd_from_locator(Bytes, Eocd_position) -> case Eocd_position < 20 of true -> {error, {archive_invalid, <<"ZIP EOCD sentinel without Zip64 locator (archive too short)"/utf8>>}}; false -> Locator_position = Eocd_position - 20, gleam@result:'try'( read_le32_at(Bytes, Locator_position), fun(Locator_sig) -> gleam@bool:guard( Locator_sig /= 16#07064b50, {error, {archive_invalid, <<"ZIP EOCD sentinel but no Zip64 EOCD locator present"/utf8>>}}, fun() -> gleam@result:'try'( read_le64_at(Bytes, Locator_position + 8), fun(Zip64_eocd_offset) -> read_zip64_eocd_record( Bytes, Zip64_eocd_offset ) end ) end ) end ) end. -file("src/packkit/zip.gleam", 1121). ?DOC( " Build a Zip64 extended-information extra-field record. Each of\n" " `uncomp` / `comp` / `local_offset` is `Some(value)` when the\n" " corresponding 32-bit slot has been replaced with the 0xFFFFFFFF\n" " sentinel; the function packs the present values in the order\n" " APPNOTE.TXT §4.5.3 prescribes.\n" ). -spec build_zip64_extra( gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer()) ) -> bitstring(). build_zip64_extra(Uncomp, Comp, Local_offset) -> Body = gleam_stdlib:bit_array_concat([case Uncomp of {some, V} -> le64(V); none -> <<>> end, case Comp of {some, V@1} -> le64(V@1); none -> <<>> end, case Local_offset of {some, V@2} -> le64(V@2); none -> <<>> end]), Body_size = erlang:byte_size(Body), gleam_stdlib:bit_array_concat([le16(16#0001), le16(Body_size), Body]). -file("src/packkit/zip.gleam", 953). -spec build_unix_ts_extra_mtime(integer()) -> bitstring(). build_unix_ts_extra_mtime(Mtime) -> Body = <<16#01, Mtime:32/little>>, gleam_stdlib:bit_array_concat( [le16(16#5455), le16(erlang:byte_size(Body)), Body] ). -file("src/packkit/zip.gleam", 989). -spec build_unix_uid_gid_extra(integer(), integer()) -> bitstring(). build_unix_uid_gid_extra(Uid, Gid) -> Body = <<1, 16#04, Uid:32/little, 16#04, Gid:32/little>>, gleam_stdlib:bit_array_concat( [le16(16#7875), le16(erlang:byte_size(Body)), Body] ). -file("src/packkit/zip.gleam", 113). -spec is_supported_method(integer()) -> boolean(). is_supported_method(Method) -> ((((((Method =:= 0) orelse (Method =:= 8)) orelse (Method =:= 12)) orelse (Method =:= 14)) orelse (Method =:= 93)) orelse (Method =:= 95)) orelse (Method =:= 99). -file("src/packkit/zip.gleam", 320). ?DOC( " Build the Zip64 EOCD record (56 bytes) followed by the Zip64 EOCD\n" " locator (20 bytes). The locator's \"Zip64 EOCD record offset\" is\n" " the byte position of the Zip64 EOCD record relative to the start\n" " of the file — i.e. `local_bytes_size + central_bytes_size` since\n" " we always write the Zip64 record immediately after the central\n" " directory and immediately before the regular EOCD.\n" ). -spec build_zip64_eocd_and_locator(integer(), integer(), integer()) -> bitstring(). build_zip64_eocd_and_locator(Count, Central_size, Central_offset) -> Zip64_eocd = gleam_stdlib:bit_array_concat( [le32(16#06064b50), le64(44), le16(16#0314), le16(45), le32(0), le32(0), le64(Count), le64(Count), le64(Central_size), le64(Central_offset)] ), Zip64_offset = Central_offset + Central_size, Zip64_locator = gleam_stdlib:bit_array_concat( [le32(16#07064b50), le32(0), le64(Zip64_offset), le32(1)] ), gleam_stdlib:bit_array_concat([Zip64_eocd, Zip64_locator]). -file("src/packkit/zip.gleam", 855). -spec dos_pair_to_unix(integer(), integer()) -> integer(). dos_pair_to_unix(Time_code, Date_code) -> Day = erlang:'band'(Date_code, 16#1F), Month = erlang:'band'(erlang:'bsr'(Date_code, 5), 16#0F), Year = erlang:'bsr'(Date_code, 9) + 1980, Second_pair = erlang:'band'(Time_code, 16#1F), Minute = erlang:'band'(erlang:'bsr'(Time_code, 5), 16#3F), Hour = erlang:'bsr'(Time_code, 11), case ((Day < 1) orelse (Month < 1)) orelse (Month > 12) of true -> 315532800; false -> Days = days_from_civil(Year, Month, Day), (((Days * 86400) + (Hour * 3600)) + (Minute * 60)) + (Second_pair * 2) end. -file("src/packkit/zip.gleam", 824). -spec unix_to_dos_pair(integer()) -> {integer(), integer()}. unix_to_dos_pair(Seconds) -> case (Seconds < 315532800) orelse (Seconds >= 4354819200) of true -> {16#0021, 16#0021}; false -> Days = Seconds div 86400, Sod = Seconds - (Days * 86400), {Year, Month, Day} = civil_from_days(Days), Hour = Sod div 3600, Minute = (Sod - (Hour * 3600)) div 60, Second = (Sod - (Hour * 3600)) - (Minute * 60), Dos_time = erlang:'bor'( erlang:'bor'(erlang:'bsl'(Hour, 11), erlang:'bsl'(Minute, 5)), Second div 2 ), Dos_date = erlang:'bor'( erlang:'bor'( erlang:'bsl'(Year - 1980, 9), erlang:'bsl'(Month, 5) ), Day ), {Dos_time, Dos_date} end. -file("src/packkit/zip.gleam", 401). -spec check_u16(integer(), binary()) -> {ok, nil} | {error, packkit@error:archive_error()}. check_u16(Value, Field) -> case (Value < 0) orelse (Value > 16#FFFF) of true -> {error, {archive_field_overflow, <<"zip "/utf8, Field/binary>>, Value, 16#FFFF}}; false -> {ok, nil} end. -file("src/packkit/zip.gleam", 413). -spec check_u32(integer(), binary()) -> {ok, nil} | {error, packkit@error:archive_error()}. check_u32(Value, Field) -> case (Value < 0) orelse (Value > 16#FFFFFFFF) of true -> {error, {archive_field_overflow, <<"zip "/utf8, Field/binary>>, Value, 16#FFFFFFFF}}; false -> {ok, nil} end. -file("src/packkit/zip.gleam", 559). -spec encode_entry(packkit@entry:entry(), integer(), method()) -> {ok, {bitstring(), bitstring(), integer()}} | {error, packkit@error:archive_error()}. encode_entry(Value, Offset, Method) -> Kind = packkit@entry:kind(Value), Raw_path = packkit@entry:to_string(packkit@entry:path(Value)), gleam@bool:guard( (Kind =:= symlink) orelse (Kind =:= hardlink), {error, {archive_entry_rejected, Raw_path, <<"ZIP encode currently supports files and directories only"/utf8>>}}, fun() -> Canonical_path = case Kind of directory -> ensure_trailing_slash(Raw_path); _ -> Raw_path end, Name_bytes = gleam_stdlib:identity(Canonical_path), Name_length = erlang:byte_size(Name_bytes), gleam@bool:guard( Name_length > 65535, {error, {archive_entry_rejected, Canonical_path, <<"ZIP entry name longer than 65535 bytes"/utf8>>}}, fun() -> Raw_body = case Kind of directory -> <<>>; _ -> packkit@entry:body(Value) end, Uncomp_size = erlang:byte_size(Raw_body), Crc = case Kind of directory -> 0; _ -> packkit@checksum:crc32(Raw_body) end, Entry_method = case Kind of directory -> store(); _ -> Method end, gleam@result:'try'(case erlang:element(2, Entry_method) of <<"store"/utf8>> -> {ok, {0, Raw_body}}; <<"deflate"/utf8>> -> _pipe = deflate_with_level( Raw_body, erlang:element(3, Entry_method) ), _pipe@1 = gleam@result:map( _pipe, fun(B) -> {8, B} end ), gleam@result:map_error( _pipe@1, fun(_capture) -> codec_to_archive_error( _capture, Canonical_path ) end ); <<"bzip2"/utf8>> -> _pipe@2 = packkit@bzip2:encode(Raw_body), _pipe@3 = gleam@result:map( _pipe@2, fun(B@1) -> {12, B@1} end ), gleam@result:map_error( _pipe@3, fun(_capture@1) -> codec_to_archive_error( _capture@1, Canonical_path ) end ); <<"lzma"/utf8>> -> {ok, {14, encode_pkware_lzma(Raw_body)}}; <<"zstd"/utf8>> -> _pipe@4 = packkit@zstd:encode(Raw_body), _pipe@5 = gleam@result:map( _pipe@4, fun(B@2) -> {93, B@2} end ), gleam@result:map_error( _pipe@5, fun(_capture@2) -> codec_to_archive_error( _capture@2, Canonical_path ) end ); <<"xz"/utf8>> -> _pipe@6 = packkit@xz:encode(Raw_body), _pipe@7 = gleam@result:map( _pipe@6, fun(B@3) -> {95, B@3} end ), gleam@result:map_error( _pipe@7, fun(_capture@3) -> codec_to_archive_error( _capture@3, Canonical_path ) end ); Other -> {error, {archive_not_implemented, <<"ZIP method "/utf8, Other/binary>>}} end, fun(_use0) -> {Method_code, Compressed_body} = _use0, Comp_size = erlang:byte_size(Compressed_body), gleam@result:'try'( check_u32(Crc, <<"crc32"/utf8>>), fun(_) -> Metadata = packkit@entry:metadata(Value), Mode = packkit@entry:mode(Metadata), External_attrs = case Kind of directory -> erlang:'bor'( 16#40000000, erlang:'bsl'(Mode, 16) ); _ -> erlang:'bsl'(Mode, 16) end, gleam@result:'try'( check_u32( External_attrs, <<"external_attributes"/utf8>> ), fun(_) -> Local_needs_zip64 = (Uncomp_size > 16#FFFFFFFF) orelse (Comp_size > 16#FFFFFFFF), Central_needs_zip64 = Local_needs_zip64 orelse (Offset > 16#FFFFFFFF), Local_uncomp_slot = case Uncomp_size > 16#FFFFFFFF of true -> 16#FFFFFFFF; false -> Uncomp_size end, Local_comp_slot = case Comp_size > 16#FFFFFFFF of true -> 16#FFFFFFFF; false -> Comp_size end, Central_offset_slot = case Offset > 16#FFFFFFFF of true -> 16#FFFFFFFF; false -> Offset end, Mtime_unix = packkit@entry:modified_at_unix( Metadata ), {Dos_time, Dos_date} = unix_to_dos_pair( Mtime_unix ), Unix_ts_extra = case Mtime_unix > 0 of true -> build_unix_ts_extra_mtime( Mtime_unix ); false -> <<>> end, Uid = packkit@entry:user_id( Metadata ), Gid = packkit@entry:group_id( Metadata ), Unix_uid_gid_extra = case (Uid > 0) orelse (Gid > 0) of true -> build_unix_uid_gid_extra( Uid, Gid ); false -> <<>> end, Zip64_local_extra = case Local_needs_zip64 of false -> <<>>; true -> build_zip64_extra( {some, Uncomp_size}, {some, Comp_size}, none ) end, Zip64_central_extra = case Central_needs_zip64 of false -> <<>>; true -> build_zip64_extra( case Uncomp_size > 16#FFFFFFFF of true -> {some, Uncomp_size}; false -> none end, case Comp_size > 16#FFFFFFFF of true -> {some, Comp_size}; false -> none end, case Offset > 16#FFFFFFFF of true -> {some, Offset}; false -> none end ) end, Local_extra = gleam_stdlib:bit_array_concat( [Zip64_local_extra, Unix_ts_extra, Unix_uid_gid_extra] ), Central_extra = gleam_stdlib:bit_array_concat( [Zip64_central_extra, Unix_ts_extra, Unix_uid_gid_extra] ), Version_needed = case {Local_needs_zip64 orelse Central_needs_zip64, Method_code} of {true, _} -> 45; {false, M} when M =:= 8 -> 20; {false, M@1} when M@1 =:= 12 -> 46; {false, M@2} when M@2 =:= 14 -> 63; {false, M@3} when M@3 =:= 93 -> 63; {false, M@4} when M@4 =:= 95 -> 63; {false, _} -> 10 end, Lzma_flag_bit = case Method_code of M@5 when M@5 =:= 14 -> 16#02; _ -> 0 end, Efs_flag_bit = case name_needs_utf8_flag( Name_bytes ) of true -> 16#0800; false -> 16#00 end, Gp_flag = erlang:'bor'( Lzma_flag_bit, Efs_flag_bit ), Local_header = gleam_stdlib:bit_array_concat( [le32(16#04034b50), le16(Version_needed), le16(Gp_flag), le16(Method_code), le16(Dos_time), le16(Dos_date), le32(Crc), le32(Local_comp_slot), le32(Local_uncomp_slot), le16(Name_length), le16( erlang:byte_size( Local_extra ) ), Name_bytes, Local_extra] ), Local_record = gleam_stdlib:bit_array_concat( [Local_header, Compressed_body] ), Local_record_size = erlang:byte_size( Local_record ), Central_record = gleam_stdlib:bit_array_concat( [le32(16#02014b50), le16(16#0314), le16(Version_needed), le16(Gp_flag), le16(Method_code), le16(Dos_time), le16(Dos_date), le32(Crc), le32(Local_comp_slot), le32(Local_uncomp_slot), le16(Name_length), le16( erlang:byte_size( Central_extra ) ), le16(0), le16(0), le16(0), le32(External_attrs), le32(Central_offset_slot), Name_bytes, Central_extra] ), {ok, {Local_record, Central_record, Local_record_size}} end ) end ) end) end ) end ). -file("src/packkit/zip.gleam", 518). ?DOC( " Walk every entry once with `list.fold`, accumulating the running\n" " byte offset alongside the local-file + central-directory record\n" " lists. Using `list.fold` here (instead of an explicit\n" " tail-recursive helper) keeps the encoder iterative on the\n" " JavaScript target, which doesn't TCO Gleam recursion and would\n" " otherwise blow its call stack on archives with tens of thousands\n" " of entries (and so couldn't ever build a Zip64-sized archive).\n" ). -spec encode_entries( list(packkit@entry:entry()), integer(), list(bitstring()), list(bitstring()), method() ) -> {ok, {list(bitstring()), list(bitstring())}} | {error, packkit@error:archive_error()}. encode_entries(Remaining, _, _, _, Method) -> Init = {encode_fold, 0, [], [], none}, Folded = gleam@list:fold( Remaining, Init, fun(State, Entry_value) -> case erlang:element(5, State) of {some, _} -> State; none -> case encode_entry( Entry_value, erlang:element(2, State), Method ) of {ok, {Local_record, Central_record, Advance}} -> {encode_fold, erlang:element(2, State) + Advance, [Local_record | erlang:element(3, State)], [Central_record | erlang:element(4, State)], none}; {error, E} -> {encode_fold, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), {some, E}} end end end ), case erlang:element(5, Folded) of {some, E@1} -> {error, E@1}; none -> {ok, {lists:reverse(erlang:element(3, Folded)), lists:reverse(erlang:element(4, Folded))}} end. -file("src/packkit/zip.gleam", 244). ?DOC( " Encode a logical archive into a ZIP byte stream using a chosen\n" " per-entry method. Supports `store`, `deflate`, `bzip2`, `zstd`,\n" " and `xz`.\n" ). -spec encode_with_method(packkit@archive:archive(), method()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. encode_with_method(Archive_value, Method) -> Entries = packkit@archive:entries(Archive_value), gleam@result:'try'( encode_entries(Entries, 0, [], [], Method), fun(_use0) -> {Local_blocks, Central_blocks} = _use0, Local_bytes = gleam_stdlib:bit_array_concat(Local_blocks), Central_bytes = gleam_stdlib:bit_array_concat(Central_blocks), Central_offset = erlang:byte_size(Local_bytes), Central_size = erlang:byte_size(Central_bytes), Count = erlang:length(Entries), Comment_bytes = case packkit@archive:comment(Archive_value) of {some, C} -> gleam_stdlib:identity(C); none -> <<>> end, Comment_size = erlang:byte_size(Comment_bytes), gleam@result:'try'( check_u16(Comment_size, <<"archive_comment_length"/utf8>>), fun(_) -> Needs_zip64_eocd = ((Count > 16#FFFF) orelse (Central_size > 16#FFFFFFFF)) orelse (Central_offset > 16#FFFFFFFF), Zip64_trailer = case Needs_zip64_eocd of false -> <<>>; true -> build_zip64_eocd_and_locator( Count, Central_size, Central_offset ) end, Eocd_total_entries = case Count > 16#FFFF of true -> 16#FFFF; false -> Count end, Eocd_central_size = case Central_size > 16#FFFFFFFF of true -> 16#FFFFFFFF; false -> Central_size end, Eocd_central_offset = case Central_offset > 16#FFFFFFFF of true -> 16#FFFFFFFF; false -> Central_offset end, Eocd = gleam_stdlib:bit_array_concat( [le32(16#06054b50), le16(0), le16(0), le16(Eocd_total_entries), le16(Eocd_total_entries), le32(Eocd_central_size), le32(Eocd_central_offset), le16(Comment_size), Comment_bytes] ), {ok, gleam_stdlib:bit_array_concat( [Local_bytes, Central_bytes, Zip64_trailer, Eocd] )} end ) end ). -file("src/packkit/zip.gleam", 235). ?DOC( " Encode a logical archive into a ZIP byte stream using the stored\n" " (uncompressed) method for every entry.\n" ). -spec encode(packkit@archive:archive()) -> {ok, bitstring()} | {error, packkit@error:archive_error()}. encode(Archive_value) -> encode_with_method(Archive_value, store()). -file("src/packkit/zip.gleam", 1194). -spec read_eocd(bitstring(), integer()) -> {ok, eocd_record()} | {error, packkit@error:archive_error()}. read_eocd(Bytes, Position) -> gleam@result:'try'( read_le16_at(Bytes, Position + 10), fun(Total_entries) -> gleam@result:'try'( read_le32_at(Bytes, Position + 12), fun(Central_size) -> gleam@result:'try'( read_le32_at(Bytes, Position + 16), fun(Central_offset) -> gleam@result:'try'( read_le16_at(Bytes, Position + 20), fun(Comment_length) -> gleam@result:'try'(case Comment_length of 0 -> {ok, none}; N -> gleam@result:'try'( slice_or_error( Bytes, Position + 22, N ), fun(Comment_bits) -> case gleam@bit_array:to_string( Comment_bits ) of {ok, Text} -> {ok, {some, Text}}; {error, _} -> {error, {archive_invalid, <<"non-UTF-8 archive comment in ZIP EOCD"/utf8>>}} end end ) end, fun(Comment) -> Needs_zip64 = ((Total_entries =:= 16#FFFF) orelse (Central_size =:= 16#FFFFFFFF)) orelse (Central_offset =:= 16#FFFFFFFF), gleam@result:'try'( case Needs_zip64 of false -> {ok, {Total_entries, Central_size, Central_offset}}; true -> gleam@result:'try'( read_zip64_eocd_from_locator( Bytes, Position ), fun(_use0) -> {Zip64_entries, Zip64_size, Zip64_offset} = _use0, {ok, {case Total_entries =:= 16#FFFF of true -> Zip64_entries; false -> Total_entries end, case Central_size =:= 16#FFFFFFFF of true -> Zip64_size; false -> Central_size end, case Central_offset =:= 16#FFFFFFFF of true -> Zip64_offset; false -> Central_offset end}} end ) end, fun(_use0@1) -> {Total_entries@1, Central_size@1, Central_offset@1} = _use0@1, {ok, {eocd_record, Total_entries@1, Central_offset@1, Central_size@1, Comment}} end ) end) end ) end ) end ) end ). -file("src/packkit/zip.gleam", 2417). -spec step_extra_field(bitstring(), integer(), integer(), integer()) -> {ok, bitstring()} | {error, nil}. step_extra_field(Rest, Size, Id, Want_id) -> gleam@result:'try'(case gleam_stdlib:bit_array_slice(Rest, 0, Size) of {ok, B} -> {ok, B}; _ -> {error, nil} end, fun(Body) -> case Id =:= Want_id of true -> {ok, Body}; false -> case gleam_stdlib:bit_array_slice( Rest, Size, erlang:byte_size(Rest) - Size ) of {ok, After} -> find_extra_field(After, Want_id); _ -> {error, nil} end end end). -file("src/packkit/zip.gleam", 2409). -spec find_extra_field(bitstring(), integer()) -> {ok, bitstring()} | {error, nil}. find_extra_field(Extra, Want_id) -> case Extra of <> -> step_extra_field(Rest, Size, Id, Want_id); _ -> {error, nil} end. -file("src/packkit/zip.gleam", 966). -spec find_unix_ts_extra_mtime(bitstring()) -> gleam@option:option(integer()). find_unix_ts_extra_mtime(Extra) -> case find_extra_field(Extra, 16#5455) of {error, _} -> none; {ok, Body} -> case Body of <> -> case erlang:'band'(Flags, 16#01) =:= 16#01 of true -> {some, Mtime}; false -> none end; _ -> none end end. -file("src/packkit/zip.gleam", 1020). -spec find_unix_uid_gid_new(bitstring()) -> gleam@option:option({integer(), integer()}). find_unix_uid_gid_new(Extra) -> option_try( find_extra_field(Extra, 16#7875), fun(Body) -> parse_unix_uid_gid_new_body(Body) end ). -file("src/packkit/zip.gleam", 1085). -spec find_unix_uid_gid_old(bitstring()) -> gleam@option:option({integer(), integer()}). find_unix_uid_gid_old(Extra) -> case find_extra_field(Extra, 16#7855) of {error, _} -> none; {ok, Body} -> case Body of <> -> {some, {Uid, Gid}}; _ -> none end end. -file("src/packkit/zip.gleam", 1013). -spec find_unix_uid_gid_in_extra(bitstring()) -> gleam@option:option({integer(), integer()}). find_unix_uid_gid_in_extra(Extra) -> case find_unix_uid_gid_new(Extra) of {some, Pair} -> {some, Pair}; none -> find_unix_uid_gid_old(Extra) end. -file("src/packkit/zip.gleam", 2002). -spec parse_aex_extra(bitstring(), binary()) -> {ok, aex_params()} | {error, packkit@error:archive_error()}. parse_aex_extra(Extras, Name) -> case find_extra_field(Extras, 16#9901) of {error, _} -> {error, {archive_invalid, <<<<"ZIP AE-x marker entry \""/utf8, Name/binary>>/binary, "\" missing the 0x9901 extra field"/utf8>>}}; {ok, Body} -> case Body of <> -> {ok, {aex_params, Version, Strength, Real_method}}; _ -> {error, {archive_invalid, <<<<"ZIP AE-x 0x9901 extra is malformed for \""/utf8, Name/binary>>/binary, "\""/utf8>>}} end end. -file("src/packkit/zip.gleam", 1929). ?DOC( " Resolve a method-99 AE-x entry to its plain compressed bytes\n" " (i.e. the bytes the real method's decoder would receive in the\n" " unencrypted case) plus the real method id. Returns `Error` when\n" " the 0x9901 extra is missing / malformed, the password fails the\n" " 2-byte verifier check, or the HMAC tag mismatches the ciphertext.\n" ). -spec resolve_aex_decryption( bitstring(), integer(), integer(), bitstring(), gleam@option:option(binary()), binary() ) -> {ok, {integer(), integer(), bitstring(), integer()}} | {error, packkit@error:archive_error()}. resolve_aex_decryption(Full, Data_offset, Total_size, Extras, Password, Name) -> gleam@result:'try'( parse_aex_extra(Extras, Name), fun(Params) -> gleam@result:'try'( aex_key_length(erlang:element(3, Params), Name), fun(Key_len) -> Salt_len = Key_len div 2, gleam@result:'try'(case Password of {some, Value} -> {ok, Value}; none -> {error, {archive_not_implemented, <<<<"encrypted ZIP entry \""/utf8, Name/binary>>/binary, "\" (use decode_with_password)"/utf8>>}} end, fun(Pwd) -> gleam@result:'try'( slice_or_error(Full, Data_offset, Total_size), fun(Raw) -> Ciphertext_len = ((Total_size - Salt_len) - 2) - 10, gleam@bool:guard( Ciphertext_len < 0, {error, {archive_invalid, <<<<"ZIP AE-x entry \""/utf8, Name/binary>>/binary, "\" is too short for salt + verifier + HMAC"/utf8>>}}, fun() -> gleam@result:'try'( slice_or_error(Raw, 0, Salt_len), fun(Salt) -> gleam@result:'try'( slice_or_error( Raw, Salt_len, 2 ), fun(Verifier) -> gleam@result:'try'( slice_or_error( Raw, Salt_len + 2, Ciphertext_len ), fun(Ciphertext) -> gleam@result:'try'( slice_or_error( Raw, (Salt_len + 2) + Ciphertext_len, 10 ), fun( Hmac_tag ) -> Derived = packkit@checksum:pbkdf2_hmac_sha1( gleam_stdlib:identity( Pwd ), Salt, 1000, (Key_len * 2) + 2 ), gleam@result:'try'( slice_or_error( Derived, 0, Key_len ), fun( Aes_key ) -> gleam@result:'try'( slice_or_error( Derived, Key_len, Key_len ), fun( Hmac_key ) -> gleam@result:'try'( slice_or_error( Derived, Key_len * 2, 2 ), fun( Expected_verifier ) -> gleam@bool:guard( Verifier /= Expected_verifier, {error, {archive_invalid, <<<<"ZIP AE-x wrong password for entry \""/utf8, Name/binary>>/binary, "\""/utf8>>}}, fun( ) -> Computed_full = packkit@checksum:hmac_sha1( Hmac_key, Ciphertext ), gleam@result:'try'( slice_or_error( Computed_full, 0, 10 ), fun( Computed_tag ) -> gleam@bool:guard( Computed_tag /= Hmac_tag, {error, {archive_invalid, <<<<"ZIP AE-x HMAC mismatch for entry \""/utf8, Name/binary>>/binary, "\""/utf8>>}}, fun( ) -> gleam@result:'try'( case packkit@internal@aes:expand_key( Aes_key ) of {ok, Value@1} -> {ok, Value@1}; {error, _} -> {error, {archive_invalid, <<<<"ZIP AE-x AES key expansion failed for \""/utf8, Name/binary>>/binary, "\""/utf8>>}} end, fun( Expanded ) -> gleam@result:'try'( aes_ctr_xor( Expanded, Ciphertext, Name ), fun( Plaintext ) -> {ok, {0, Ciphertext_len, Plaintext, erlang:element( 4, Params )}} end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end) end ) end ). -file("src/packkit/zip.gleam", 2391). ?DOC( " Parse the Zip64 extended-information extra field (header_id 0x0001)\n" " out of a packed extra-field block. Only the slots that hit the\n" " 0xFFFFFFFF sentinel in the surrounding central-directory or local\n" " file header have 8-byte values present in the extra-field body,\n" " and they appear in a fixed order (uncomp_size, comp_size,\n" " local_offset, disk_start). The decoder walks the extra-field\n" " chain and picks the first record with `header_id = 0x0001`.\n" ). -spec parse_zip64_extra(bitstring(), boolean(), boolean(), boolean()) -> {ok, {gleam@option:option(integer()), gleam@option:option(integer()), gleam@option:option(integer())}} | {error, packkit@error:archive_error()}. parse_zip64_extra( Extra, Uncomp_at_sentinel, Comp_at_sentinel, Offset_at_sentinel ) -> case find_extra_field(Extra, 16#0001) of {ok, Payload} -> decode_zip64_extra_payload( Payload, Uncomp_at_sentinel, Comp_at_sentinel, Offset_at_sentinel ); _ -> {ok, {none, none, none}} end. -file("src/packkit/zip.gleam", 1506). -spec read_local_entry( bitstring(), integer(), binary(), integer(), integer(), integer(), integer(), packkit@limit:limits(), integer(), gleam@option:option(binary()), integer(), integer(), integer() ) -> {ok, packkit@entry:entry()} | {error, packkit@error:archive_error()}. read_local_entry( Full, Local_offset, Name, Expected_crc, Uncomp_size, Comp_size, External_attrs, Limits, Gp_flag, Password, Mtime_unix, Owner_uid, Owner_gid ) -> gleam@result:'try'( read_le32_at(Full, Local_offset), fun(Signature) -> gleam@bool:guard( Signature /= 16#04034b50, {error, {archive_invalid, <<"missing local file header signature"/utf8>>}}, fun() -> Encrypted = erlang:'band'(Gp_flag, 1) =:= 1, Strong_encrypted = erlang:'band'(Gp_flag, 16#40) /= 0, gleam@bool:guard( Strong_encrypted, {error, {archive_not_implemented, <<"ZIP strong-encryption (gp flag bit 6)"/utf8>>}}, fun() -> gleam@result:'try'( read_le16_at(Full, Local_offset + 8), fun(Method) -> gleam@bool:guard( not is_supported_method(Method), {error, {archive_not_implemented, <<"ZIP method "/utf8, (erlang:integer_to_binary( Method ))/binary>>}}, fun() -> gleam@result:'try'( read_le16_at( Full, Local_offset + 26 ), fun(Local_name_length) -> gleam@result:'try'( read_le16_at( Full, Local_offset + 28 ), fun(Local_extra_length) -> gleam@result:'try'( read_le32_at( Full, Local_offset + 22 ), fun( Local_uncomp ) -> gleam@result:'try'( read_le32_at( Full, Local_offset + 18 ), fun( Local_comp ) -> gleam@result:'try'( slice_or_error( Full, (Local_offset + 30) + Local_name_length, Local_extra_length ), fun( Local_extra_bits ) -> gleam@result:'try'( parse_zip64_extra( Local_extra_bits, Local_uncomp =:= 16#FFFFFFFF, Local_comp =:= 16#FFFFFFFF, false ), fun( _use0 ) -> {Local_zip64_uncomp, Local_zip64_comp, _} = _use0, Uncomp_size@1 = case {Uncomp_size, Local_zip64_uncomp} of {N, {some, V}} when N =:= 16#FFFFFFFF -> V; {N@1, _} -> N@1 end, Comp_size@1 = case {Comp_size, Local_zip64_comp} of {N@2, {some, V@1}} when N@2 =:= 16#FFFFFFFF -> V@1; {N@3, _} -> N@3 end, Data_offset = ((Local_offset + 30) + Local_name_length) + Local_extra_length, Is_aex = Encrypted andalso (Method =:= 99), gleam@result:'try'( case Is_aex of true -> resolve_aex_decryption( Full, Data_offset, Comp_size@1, Local_extra_bits, Password, Name ); false -> _pipe = resolve_pkware_decryption( Full, Data_offset, Comp_size@1, Encrypted, Expected_crc, Password, Name ), gleam@result:map( _pipe, fun( Triple ) -> {Offset, Size, Source} = Triple, {Offset, Size, Source, Method} end ) end, fun( _use0@1 ) -> {Data_offset@1, Comp_size@2, Body_source, Method@1} = _use0@1, gleam@result:'try'( case Method@1 of M when M =:= 0 -> slice_or_error( Body_source, Data_offset@1, Uncomp_size@1 ); M@1 when M@1 =:= 8 -> gleam@result:'try'( slice_or_error( Body_source, Data_offset@1, Comp_size@2 ), fun( Compressed ) -> _pipe@1 = packkit@deflate:decode_with_limits( Compressed, Limits ), gleam@result:map_error( _pipe@1, fun( _capture ) -> codec_to_archive_error( _capture, Name ) end ) end ); M@2 when M@2 =:= 12 -> gleam@result:'try'( slice_or_error( Body_source, Data_offset@1, Comp_size@2 ), fun( Compressed@1 ) -> _pipe@2 = packkit@bzip2:decode_with_limits( Compressed@1, Limits ), gleam@result:map_error( _pipe@2, fun( _capture@1 ) -> codec_to_archive_error( _capture@1, Name ) end ) end ); M@3 when M@3 =:= 14 -> gleam@result:'try'( slice_or_error( Body_source, Data_offset@1, Comp_size@2 ), fun( Compressed@2 ) -> _pipe@3 = decode_pkware_lzma( Compressed@2, Uncomp_size@1, Limits ), gleam@result:map_error( _pipe@3, fun( _capture@2 ) -> codec_to_archive_error( _capture@2, Name ) end ) end ); M@4 when M@4 =:= 93 -> gleam@result:'try'( slice_or_error( Body_source, Data_offset@1, Comp_size@2 ), fun( Compressed@3 ) -> _pipe@4 = packkit@zstd:decode_with_limits( Compressed@3, Limits ), gleam@result:map_error( _pipe@4, fun( _capture@3 ) -> codec_to_archive_error( _capture@3, Name ) end ) end ); M@5 when M@5 =:= 95 -> gleam@result:'try'( slice_or_error( Body_source, Data_offset@1, Comp_size@2 ), fun( Compressed@4 ) -> _pipe@5 = packkit@xz:decode_with_limits( Compressed@4, Limits ), gleam@result:map_error( _pipe@5, fun( _capture@4 ) -> codec_to_archive_error( _capture@4, Name ) end ) end ); Other -> {error, {archive_not_implemented, <<"ZIP method "/utf8, (erlang:integer_to_binary( Other ))/binary>>}} end, fun( Body ) -> Skip_crc = Is_aex andalso (Expected_crc =:= 0), gleam@bool:guard( not Skip_crc andalso (packkit@checksum:crc32( Body ) /= Expected_crc), {error, {archive_invalid, <<"ZIP CRC32 mismatch"/utf8>>}}, fun( ) -> Is_directory = gleam_stdlib:string_ends_with( Name, <<"/"/utf8>> ), Mode = erlang:'band'( erlang:'bsr'( External_attrs, 16 ), 16#FFFF ), Apply_metadata = fun( E ) -> With_mode = case Mode of 0 -> E; _ -> packkit@entry:with_mode( E, Mode ) end, With_mtime = case Mtime_unix > 0 of true -> packkit@entry:with_modified_at( With_mode, Mtime_unix ); false -> With_mode end, case (Owner_uid > 0) orelse (Owner_gid > 0) of true -> packkit@entry:with_owner( With_mtime, Owner_uid, Owner_gid ); false -> With_mtime end end, case Is_directory of true -> _pipe@6 = packkit@entry:directory_checked( strip_trailing_slash( Name ) ), _pipe@7 = gleam@result:map_error( _pipe@6, fun( _capture@5 ) -> entry_error_to_archive_error( _capture@5, Name ) end ), gleam@result:map( _pipe@7, Apply_metadata ); false -> _pipe@8 = packkit@entry:file_checked( Name, Body ), _pipe@9 = gleam@result:map_error( _pipe@8, fun( _capture@6 ) -> entry_error_to_archive_error( _capture@6, Name ) end ), gleam@result:map( _pipe@9, Apply_metadata ) end end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ). -file("src/packkit/zip.gleam", 1318). -spec parse_central_directory( bitstring(), list(packkit@entry:entry()), integer(), bitstring(), integer(), packkit@limit:limits(), gleam@option:option(binary()) ) -> {ok, list(packkit@entry:entry())} | {error, packkit@error:archive_error()}. parse_central_directory( Bytes, Acc, Remaining, Full, Accumulated_body_bytes, Limits, Password ) -> case Remaining of 0 -> {ok, Acc}; _ -> gleam@result:'try'( read_le32_at(Bytes, 0), fun(Signature) -> gleam@bool:guard( Signature /= 16#02014b50, {error, {archive_invalid, <<"missing ZIP central directory signature"/utf8>>}}, fun() -> gleam@result:'try'( read_le16_at(Bytes, 8), fun(Gp_flag) -> gleam@result:'try'( read_le16_at(Bytes, 10), fun(Method) -> gleam@result:'try'( read_le16_at(Bytes, 12), fun(Dos_time) -> gleam@result:'try'( read_le16_at(Bytes, 14), fun(Dos_date) -> gleam@result:'try'( read_le32_at( Bytes, 16 ), fun(Crc) -> gleam@result:'try'( read_le32_at( Bytes, 20 ), fun( Comp_size ) -> gleam@result:'try'( read_le32_at( Bytes, 24 ), fun( Uncomp_size ) -> gleam@result:'try'( read_le16_at( Bytes, 28 ), fun( Name_length ) -> gleam@result:'try'( read_le16_at( Bytes, 30 ), fun( Extra_length ) -> gleam@result:'try'( read_le16_at( Bytes, 32 ), fun( Comment_length ) -> gleam@result:'try'( read_le32_at( Bytes, 38 ), fun( External_attrs ) -> gleam@result:'try'( read_le32_at( Bytes, 42 ), fun( Local_offset ) -> Name_offset = 46, gleam@result:'try'( slice_or_error( Bytes, Name_offset, Name_length ), fun( Name_bits ) -> gleam@result:'try'( bytes_to_string( Name_bits ), fun( Name ) -> gleam@result:'try'( slice_or_error( Bytes, Name_offset + Name_length, Extra_length ), fun( Extra_bits ) -> Uncomp_at_sentinel = Uncomp_size =:= 16#FFFFFFFF, Comp_at_sentinel = Comp_size =:= 16#FFFFFFFF, Offset_at_sentinel = Local_offset =:= 16#FFFFFFFF, gleam@result:'try'( parse_zip64_extra( Extra_bits, Uncomp_at_sentinel, Comp_at_sentinel, Offset_at_sentinel ), fun( _use0 ) -> {Zip64_uncomp, Zip64_comp, Zip64_offset} = _use0, Uncomp_size@1 = case Zip64_uncomp of {some, V} -> V; none -> Uncomp_size end, Comp_size@1 = case Zip64_comp of {some, V@1} -> V@1; none -> Comp_size end, Local_offset@1 = case Zip64_offset of {some, V@2} -> V@2; none -> Local_offset end, Mtime_resolved = case find_unix_ts_extra_mtime( Extra_bits ) of {some, V@3} when V@3 >= 0 -> V@3; _ -> case (Dos_time =:= 16#0021) andalso (Dos_date =:= 16#0021) of true -> 0; false -> dos_pair_to_unix( Dos_time, Dos_date ) end end, {Owner_uid, Owner_gid} = case find_unix_uid_gid_in_extra( Extra_bits ) of {some, {Uid, Gid}} when (Uid >= 0) andalso (Gid >= 0) -> {Uid, Gid}; _ -> {0, 0} end, gleam@bool:guard( erlang:byte_size( Name ) > packkit@limit:max_entry_name_bytes( Limits ), {error, {archive_limit_exceeded, <<"max_entry_name_bytes"/utf8>>, erlang:byte_size( Name )}}, fun( ) -> Cleaned_name = strip_trailing_slash( Name ), Depth = path_depth( Cleaned_name ), gleam@bool:guard( Depth > packkit@limit:max_entry_depth( Limits ), {error, {archive_limit_exceeded, <<"max_entry_depth"/utf8>>, Depth}}, fun( ) -> gleam@bool:guard( not is_supported_method( Method ), {error, {archive_not_implemented, <<"ZIP method "/utf8, (erlang:integer_to_binary( Method ))/binary>>}}, fun( ) -> gleam@result:'try'( read_local_entry( Full, Local_offset@1, Name, Crc, Uncomp_size@1, Comp_size@1, External_attrs, Limits, Gp_flag, Password, Mtime_resolved, Owner_uid, Owner_gid ), fun( Entry_value ) -> Next_total = Accumulated_body_bytes + erlang:byte_size( packkit@entry:body( Entry_value ) ), gleam@bool:guard( Next_total > packkit@limit:max_output_bytes( Limits ), {error, {archive_limit_exceeded, <<"max_output_bytes"/utf8>>, Next_total}}, fun( ) -> Record_size = ((46 + Name_length) + Extra_length) + Comment_length, Next_bits = case gleam_stdlib:bit_array_slice( Bytes, Record_size, erlang:byte_size( Bytes ) - Record_size ) of {ok, Value} -> Value; {error, _} -> <<>> end, parse_central_directory( Next_bits, [Entry_value | Acc], Remaining - 1, Full, Next_total, Limits, Password ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end ) end. -file("src/packkit/zip.gleam", 463). -spec decode_internal( bitstring(), gleam@option:option(binary()), packkit@limit:limits() ) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode_internal(Bytes, Password, 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() -> gleam@result:'try'( locate_eocd(Bytes), fun(Eocd_offset) -> gleam@result:'try'( read_eocd(Bytes, Eocd_offset), fun(Eocd) -> gleam@bool:guard( erlang:element(2, Eocd) > packkit@limit:max_members( Limits ), {error, {archive_limit_exceeded, <<"max_members"/utf8>>, erlang:element(2, Eocd)}}, fun() -> gleam@result:'try'( slice_or_error( Bytes, erlang:element(3, Eocd), erlang:element(4, Eocd) ), fun(Central_bits) -> gleam@result:'try'( parse_central_directory( Central_bits, [], erlang:element(2, Eocd), Bytes, 0, Limits, Password ), fun(Entries) -> Base = packkit@archive:from_entries( format(), lists:reverse(Entries) ), case erlang:element(5, Eocd) of {some, C} -> {ok, packkit@archive:with_comment( Base, C )}; none -> {ok, Base} end end ) end ) end ) end ) end ) end ). -file("src/packkit/zip.gleam", 426). ?DOC(" Decode a ZIP archive using default limits.\n"). -spec decode(bitstring()) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode(Bytes) -> decode_internal(Bytes, none, packkit@limit:default()). -file("src/packkit/zip.gleam", 433). ?DOC(" Decode a ZIP archive using explicit limits.\n"). -spec decode_with_limits(bitstring(), packkit@limit:limits()) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode_with_limits(Bytes, Limits) -> decode_internal(Bytes, none, Limits). -file("src/packkit/zip.gleam", 447). ?DOC( " Decode a ZIP archive whose entries may be protected by the\n" " PKWARE traditional (\"ZipCrypto\") encryption scheme. The\n" " password is applied to every encrypted entry; entries without\n" " the gp-flag encryption bit decode unchanged. Wrong-password\n" " detection relies on the 12-byte encryption header check byte\n" " (the high byte of the entry's CRC-32), so a wrong password\n" " surfaces as `ArchiveInvalid`.\n" ). -spec decode_with_password(bitstring(), binary()) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode_with_password(Bytes, Password) -> decode_internal(Bytes, {some, Password}, packkit@limit:default()). -file("src/packkit/zip.gleam", 455). ?DOC(" Same as `decode_with_password` but with explicit limits.\n"). -spec decode_with_password_and_limits( bitstring(), binary(), packkit@limit:limits() ) -> {ok, packkit@archive:archive()} | {error, packkit@error:archive_error()}. decode_with_password_and_limits(Bytes, Password, Limits) -> decode_internal(Bytes, {some, Password}, Limits).