Skip to main content

src/packkit@zip.erl

-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, <<Value, Rest/binary>>} ->
            {some, {Value, Rest}};

        {2, <<Value@1:16/little, Rest@1/binary>>} ->
            {some, {Value@1, Rest@1}};

        {4, <<Value@2:32/little, Rest@2/binary>>} ->
            {some, {Value@2, Rest@2}};

        {8, <<Value@3:64/little, Rest@3/binary>>} ->
            {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
        <<Gid_size, Gid_rest/binary>> ->
            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 ->
            <<Value/binary, "/"/utf8>>
    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
        <<B, Rest/binary>> ->
            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
        <<B, Rest/binary>> ->
            {Plain, Keys@1} = pkware_decrypt_byte(Keys, B),
            pkware_decrypt_bytes(Keys@1, Rest, <<Acc/bitstring, Plain>>);

        _ ->
            {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
        <<B, Rest/binary>> ->
            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
        {<<L, L_rest/binary>>, <<R, R_rest/binary>>} ->
            Mixed = erlang:'bxor'(L, R),
            xor_bit_arrays_zip(
                L_rest,
                R_rest,
                gleam_stdlib:bit_array_concat([Acc, <<Mixed>>])
            );

        {_, _} ->
            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
        <<Prop_byte, _:32/little-unsigned>> ->
            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, <<Prop_byte>>, 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 = <<Counter:64/little, 0:64/little>>,
            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
        <<B, Rest/binary>> ->
            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) ->
    <<Value:16/little>>.

-file("src/packkit/zip.gleam", 2348).
-spec le32(integer()) -> bitstring().
le32(Value) ->
    <<Value:32/little>>.

-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, <<Value:16/little>>} ->
            {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, <<Value:32/little>>} ->
            {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
        <<Value:32/little>> ->
            {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, <<Value:64/little>>} ->
            {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) ->
    <<Value:64/little>>.

-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
                <<Value:64/little, Rest/binary>> ->
                    {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
        <<Id:16/little, Size:16/little, Rest/binary>> ->
            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
                <<Flags, Mtime:32/little, _/binary>> ->
                    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
                <<Uid:16/little, Gid:16/little, _/binary>> ->
                    {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
                <<Version:16/little,
                    16#41,
                    16#45,
                    Strength,
                    Real_method:16/little>> ->
                    {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).