diff options
37 files changed, 8536 insertions, 40 deletions
@@ -21,3 +21,6 @@ priv/templates/*.dtl.erl ebin .edts env + +# hex_core artifact +src/r3_safe_erl_term.erl @@ -15,6 +15,11 @@ main(_) -> %% a changed structure from an older one rm_rf("_build/bootstrap"), + %% We fetch a few deps from hex for boostraping, + %% so we must compile r3_safe_erl_term.xrl which + %% is part of hex_core. + compile_xrl_file("src/r3_safe_erl_term.xrl"), + %% Fetch and build deps required to build rebar3 BaseDeps = [{providers, []} ,{getopt, []} @@ -22,8 +27,7 @@ main(_) -> ,{erlware_commons, ["ec_dictionary.erl", "ec_vsn.erl"]} ,{parse_trans, ["parse_trans.erl", "parse_trans_pp.erl", "parse_trans_codegen.erl"]} - ,{certifi, []} - ,{hex_core, []}], + ,{certifi, []}], Deps = get_deps(), [fetch_and_compile(Dep, Deps) || Dep <- BaseDeps], @@ -143,7 +147,7 @@ compile(App, FirstFiles) -> [compile_xrl_file(X) || X <- LeexFiles], YeccFiles = filelib:wildcard(filename:join([Dir, "src", "*.yrl"])), [compile_yrl_file(X) || X <- YeccFiles], - Sources = FirstFilesPaths ++ filelib:wildcard(filename:join([Dir, "src", "*.erl"])), + Sources = FirstFilesPaths ++ filelib:wildcard(filename:join([Dir, "src", "*.erl"])), [compile_erl_file(X, [{i, filename:join(Dir, "include")} ,debug_info ,{outdir, filename:join(Dir, "ebin")} @@ -175,7 +179,7 @@ bootstrap_rebar3() -> Res = symlink_or_copy(filename:absname("src"), filename:absname("_build/default/lib/rebar/src")), true = Res == ok orelse Res == exists, - Sources = ["src/rebar_resource_v2.erl", "src/rebar_resource.erl" | filelib:wildcard("src/*.erl")], + Sources = ["src/rebar_resource_v2.erl", "src/rebar_resource.erl" | filelib:wildcard("src/*.erl") ], [compile_erl_file(X, [{outdir, "_build/default/lib/rebar/ebin/"} ,return | additional_defines()]) || X <- Sources], code:add_patha(filename:absname("_build/default/lib/rebar/ebin")). diff --git a/rebar.config b/rebar.config index 1f18d97..2654919 100644 --- a/rebar.config +++ b/rebar.config @@ -11,7 +11,6 @@ {relx, "3.28.0"}, {cf, "0.2.2"}, {cth_readable, "1.4.3"}, - {hex_core, "0.4.0"}, {eunit_formatters, "0.5.0"}]}. {post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", @@ -6,7 +6,6 @@ {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.3.1">>},0}, {<<"eunit_formatters">>,{pkg,<<"eunit_formatters">>,<<"0.5.0">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, - {<<"hex_core">>,{pkg,<<"hex_core">>,<<"0.4.0">>},0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},0}, {<<"providers">>,{pkg,<<"providers">>,<<"1.7.0">>},0}, {<<"relx">>,{pkg,<<"relx">>,<<"3.28.0">>},0}, @@ -20,7 +19,6 @@ {<<"erlware_commons">>, <<"0CE192AD69BC6FD0880246D852D0ECE17631E234878011D1586E053641ED4C04">>}, {<<"eunit_formatters">>, <<"6A9133943D36A465D804C1C5B6E6839030434B8879C5600D7DDB5B3BAD4CCB59">>}, {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}, - {<<"hex_core">>, <<"6A0E0B1B519850344292298DA1BFA685E71534FDF4C69434993039F81078C7FA">>}, {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, {<<"providers">>, <<"BBF730563914328EC2511D205E6477A94831DB7297DE313B3872A2B26C562EAB">>}, {<<"relx">>, <<"CFC26899E308FAB79B5826C4298EC052A2C1D66D03679AB76E6266D766B56545">>}, diff --git a/src/r3_hex_api.erl b/src/r3_hex_api.erl new file mode 100644 index 0000000..09d9bcf --- /dev/null +++ b/src/r3_hex_api.erl @@ -0,0 +1,120 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @hidden + +-module(r3_hex_api). + +-export([ + delete/2, + get/2, + post/3, + put/3, + encode_query_string/1, + build_repository_path/2, + build_organization_path/2, + join_path_segments/1 +]). +-define(ERL_CONTENT_TYPE, <<"application/vnd.hex+erlang">>). + +get(Config, Path) -> + request(Config, get, Path, undefined). + +post(Config, Path, Body) -> + request(Config, post, Path, encode_body(Body)). + +put(Config, Path, Body) -> + request(Config, put, Path, encode_body(Body)). + +delete(Config, Path) -> + request(Config, delete, Path, undefined). + +%% @private +encode_query_string(List) -> + QueryString = + join("&", + lists:map(fun + ({K, V}) when is_atom(V) -> + atom_to_list(K) ++ "=" ++ atom_to_list(V); + ({K, V}) when is_binary(V) -> + atom_to_list(K) ++ "=" ++ binary_to_list(V); + ({K, V}) when is_integer(V) -> + atom_to_list(K) ++ "=" ++ integer_to_list(V) + end, List)), + Encoded = http_uri:encode(QueryString), + list_to_binary(Encoded). + +%% @private +build_repository_path(#{api_repository := Repo}, Path) when is_binary(Repo) -> + ["repos", Repo | Path]; +build_repository_path(#{api_repository := undefined}, Path) -> + Path. + +%% @private +build_organization_path(#{api_organization := Org}, Path) when is_binary(Org) -> + ["orgs", Org | Path]; +build_organization_path(#{api_organization := undefined}, Path) -> + Path. + +%% @private +join_path_segments(Segments) -> + erlang:iolist_to_binary(join(<<"/">>, lists:map(fun encode/1, Segments))). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +request(Config, Method, PathSegments, Body) when is_list(PathSegments) -> + Path = join_path_segments(PathSegments), + request(Config, Method, Path, Body); +request(Config, Method, Path, Body) when is_binary(Path) and is_map(Config) -> + DefaultHeaders = make_headers(Config), + ReqHeaders = maps:merge(maps:get(http_headers, Config, #{}), DefaultHeaders), + ReqHeaders2 = put_new(<<"accept">>, ?ERL_CONTENT_TYPE, ReqHeaders), + + case r3_hex_http:request(Config, Method, build_url(Path, Config), ReqHeaders2, Body) of + {ok, {Status, RespHeaders, RespBody}} = Response -> + ContentType = maps:get(<<"content-type">>, RespHeaders, <<"">>), + case binary:match(ContentType, ?ERL_CONTENT_TYPE) of + {_, _} -> + {ok, {Status, RespHeaders, binary_to_term(RespBody)}}; + + nomatch -> + Response + end; + + Other -> + Other + end. + +encode(Binary) when is_binary(Binary) -> + encode(binary_to_list(Binary)); +encode(String) when is_list(String) -> + http_uri:encode(String). + +build_url(Path, #{api_url := URI}) -> + <<URI/binary, "/", Path/binary>>. + +encode_body({_ContentType, _Body} = Body) -> + Body; +encode_body(Body) -> + {binary_to_list(?ERL_CONTENT_TYPE), term_to_binary(Body)}. + +%% TODO: copy-pasted from r3_hex_repo +make_headers(Config) -> + maps:fold(fun set_header/3, #{}, Config). + +set_header(api_key, Token, Headers) when is_binary(Token) -> maps:put(<<"authorization">>, Token, Headers); +set_header(_, _, Headers) -> Headers. + +put_new(Key, Value, Map) -> + case maps:find(Key, Map) of + {ok, _} -> Map; + error -> maps:put(Key, Value, Map) + end. + +%% https://github.com/erlang/otp/blob/OTP-20.3/lib/stdlib/src/lists.erl#L1449:L1453 +join(_Sep, []) -> []; +join(Sep, [H|T]) -> [H|join_prepend(Sep, T)]. + +join_prepend(_Sep, []) -> []; +join_prepend(Sep, [H|T]) -> [Sep,H|join_prepend(Sep,T)]. diff --git a/src/r3_hex_api_key.erl b/src/r3_hex_api_key.erl new file mode 100644 index 0000000..68c0b13 --- /dev/null +++ b/src/r3_hex_api_key.erl @@ -0,0 +1,31 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_key). +-export([ + list/1, + get/2, + add/3, + delete/2, + delete_all/1 +]). + +list(Config) when is_map(Config) -> + Path = r3_hex_api:build_organization_path(Config, ["keys"]), + r3_hex_api:get(Config, Path). + +get(Config, Name) when is_map(Config) -> + Path = r3_hex_api:build_organization_path(Config, ["keys", Name]), + r3_hex_api:get(Config, Path). + +add(Config, Name, Permissions) when is_map(Config) -> + Path = r3_hex_api:build_organization_path(Config, ["keys"]), + Params = #{<<"name">> => Name, <<"permissions">> => Permissions}, + r3_hex_api:post(Config, Path, Params). + +delete(Config, Name) when is_map(Config) -> + Path = r3_hex_api:build_organization_path(Config, ["keys", Name]), + r3_hex_api:delete(Config, Path). + +delete_all(Config) when is_map(Config) -> + Path = r3_hex_api:build_organization_path(Config, ["keys"]), + r3_hex_api:delete(Config, Path). diff --git a/src/r3_hex_api_package.erl b/src/r3_hex_api_package.erl new file mode 100644 index 0000000..903adc0 --- /dev/null +++ b/src/r3_hex_api_package.erl @@ -0,0 +1,49 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_package). +-export([get/2, search/3]). + +%% @doc +%% Gets package. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_package:get(r3_hex_core:default_config(), <<"package">>). +%% {ok, {200, ..., #{ +%% <<"name">> => <<"package1">>, +%% <<"meta">> => #{ +%% <<"description">> => ..., +%% <<"licenses">> => ..., +%% <<"links">> => ..., +%% <<"maintainers">> => ... +%% }, +%% ..., +%% <<"releases">> => [ +%% #{<<"url">> => ..., <<"version">> => <<"0.5.0">>}], +%% #{<<"url">> => ..., <<"version">> => <<"1.0.0">>}], +%% ... +%% ]}}} +%% ''' +%% @end +get(Config, Name) when is_binary(Name) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", Name]), + r3_hex_api:get(Config, Path). + +%% @doc +%% Searches packages. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_package:search(r3_hex_core:default_config(), <<"package">>, []). +%% {ok, {200, ..., [ +%% #{<<"name">> => <<"package1">>, ...}, +%% ... +%% ]}} +%% ''' +search(Config, Query, SearchParams) when is_binary(Query) and is_list(SearchParams) and is_map(Config) -> + QueryString = r3_hex_api:encode_query_string([{search, Query} | SearchParams]), + Path = r3_hex_api:join_path_segments(r3_hex_api:build_repository_path(Config, ["packages"])), + PathQuery = <<Path/binary, "?", QueryString/binary>>, + r3_hex_api:get(Config, PathQuery). diff --git a/src/r3_hex_api_package_owner.erl b/src/r3_hex_api_package_owner.erl new file mode 100644 index 0000000..3a83b43 --- /dev/null +++ b/src/r3_hex_api_package_owner.erl @@ -0,0 +1,34 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_package_owner). +-export([ + add/3, + delete/3, + get/3, + list/2 +]). + +%% Examples: +%% +%% ``` +%% > r3_hex_api_owner:list(r3_hex_core:default_config(), <<"package">>). +%% {ok, {200, ..., [ +%% #{<<"username">> => <<"alice">>, ...}, +%% ... +%% ]}} +%% ''' +list(Config, PackageName) when is_binary(PackageName) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners"]), + r3_hex_api:get(Config, Path). + +get(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), + r3_hex_api:get(Config, Path). + +add(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), + r3_hex_api:put(Config, Path, #{}). + +delete(Config, PackageName, UsernameOrEmail) when is_binary(PackageName) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", PackageName, "owners", UsernameOrEmail]), + r3_hex_api:delete(Config, Path). diff --git a/src/r3_hex_api_release.erl b/src/r3_hex_api_release.erl new file mode 100644 index 0000000..4acda0e --- /dev/null +++ b/src/r3_hex_api_release.erl @@ -0,0 +1,60 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_release). +-export([ + delete/3, + get/3, + publish/2, + retire/4, + unretire/3 +]). + +%% @doc +%% Gets package release. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api:get_release(<<"package">>, <<"1.0.0">>, r3_hex_core:default_config()). +%% {ok, {200, ..., #{ +%% <<"version">> => <<"1.0.0">>, +%% <<"meta">> => #{ +%% <<"description">> => ..., +%% <<"licenses">> => ..., +%% <<"links">> => ..., +%% <<"maintainers">> => ... +%% }, +%% ...}}} +%% ''' +%% @end +get(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version]), + r3_hex_api:get(Config, Path). + +publish(Config, Tarball) when is_binary(Tarball) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["publish"]), + TarballContentType = "application/octet-stream", + Config2 = put_header(<<"content-length">>, integer_to_binary(byte_size(Tarball)), Config), + Body = {TarballContentType, Tarball}, + r3_hex_api:post(Config2, Path, Body). + +delete(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version]), + r3_hex_api:delete(Config, Path). + +retire(Config, Name, Version, Params) when is_binary(Name) and is_binary(Version) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version, "retire"]), + r3_hex_api:post(Config, Path, Params). + +unretire(Config, Name, Version) when is_binary(Name) and is_binary(Version) and is_map(Config) -> + Path = r3_hex_api:build_repository_path(Config, ["packages", Name, "releases", Version, "retire"]), + r3_hex_api:delete(Config, Path). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +put_header(Name, Value, Config) -> + Headers = maps:get(http_headers, Config, #{}), + Headers2 = maps:put(Name, Value, Headers), + maps:put(http_headers, Headers2, Config). diff --git a/src/r3_hex_api_user.erl b/src/r3_hex_api_user.erl new file mode 100644 index 0000000..67d32ce --- /dev/null +++ b/src/r3_hex_api_user.erl @@ -0,0 +1,46 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_api_user). +-export([ + create/4, + get/2, + me/1, + reset_password/2 +]). + +me(Config) when is_map(Config) -> + r3_hex_api:get(Config, ["users", "me"]). + +create(Config, Username, Password, Email) -> + Params = #{ + <<"username">> => Username, + <<"password">> => Password, + <<"email">> => Email + }, + r3_hex_api:post(Config, ["users"], Params). + +reset_password(Username, Config) when is_binary(Username) and is_map(Config) -> + r3_hex_api:post(Config, ["users", Username, "reset"], #{}). + +%% @doc +%% Gets user. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_api_user:get(<<"user">>, r3_hex_core:default_config()). +%% {ok, {200, ..., #{ +%% <<"username">> => <<"user">>, +%% <<"packages">> => [ +%% #{ +%% <<"name">> => ..., +%% <<"url">> => ..., +%% ... +%% }, +%% ... +%% ], +%% ...}}} +%% ''' +%% @end +get(Config, Username) when is_binary(Username) and is_map(Config) -> + r3_hex_api:get(Config, ["users", Username]). diff --git a/src/r3_hex_core.erl b/src/r3_hex_core.erl new file mode 100644 index 0000000..84eabe6 --- /dev/null +++ b/src/r3_hex_core.erl @@ -0,0 +1,92 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @doc +%% hex_core entrypoint module. +%% +%% ### Config +%% +%% Most functions in the hex_core API takes a configuration. The configuration sets things +%% like HTTP client to use, and API and repository URL. Some of these configuration options +%% will likely be static for your application and some may change depending on the function +%% you call. +%% +%% ##### Options +%% +%% * `api_key' - Authentication key used when accessing the HTTP API. +%% * `api_organization' - Name of the organization endpoint in the API, this should +%% for example be set when accessing key for a specific organization. +%% * `api_repository' - Name of the repository endpoint in the API, this should +%% for example be set when accessing packages from a specific repository. +%% * `api_url' - URL to the HTTP API (default: `https://hex.pm/api'). +%% * `http_adapter' - Callback module used for HTTP requests, see [`r3_hex_http'](r3_hex_http.html) +%% (default: `r3_hex_http_httpc'). +%% * `http_etag' - Sets the `if-none-match' HTTP header with the given value to do a +%% conditional HTTP request. +%% * `http_adapter_config' - Configuration to pass to the HTTP adapter. +%% * `http_user_agent_fragment' - Will be appended to the `user-agent` HTTP header (default: `(httpc)'). +%% * `repo_key' - Authentication key used when accessing the repository. +%% * `repo_name' - Name of the repository, used for verifying the repository signature +%% authenticity (default: `hexpm'). +%% * `repo_public_key' - Public key used to verify the repository signature +%% (defaults to hexpm public key `https://hex.pm/docs/public_keys'). +%% * `repo_url' - URL to the repository (default: `https://repo.hex.pm'). +%% * `repo_organization' - Name of the organization repository, appends `/repos/:name' +%% to the repository URL and overrides the `repo_name' option. +%% * `repo_verify' - If `true' will verify the repository signature (default: `true'). +%% * `repo_verify_origin' - If `true' will verify the repository signature origin, +%% requires protobuf messages as of hex_core v0.4.0 (default: `true'). + +-module(r3_hex_core). +-export([default_config/0]). + +-export_type([config/0]). + +%% https://hex.pm/docs/public_keys +-define(HEXPM_PUBLIC_KEY, <<"-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApqREcFDt5vV21JVe2QNB +Edvzk6w36aNFhVGWN5toNJRjRJ6m4hIuG4KaXtDWVLjnvct6MYMfqhC79HAGwyF+ +IqR6Q6a5bbFSsImgBJwz1oadoVKD6ZNetAuCIK84cjMrEFRkELtEIPNHblCzUkkM +3rS9+DPlnfG8hBvGi6tvQIuZmXGCxF/73hU0/MyGhbmEjIKRtG6b0sJYKelRLTPW +XgK7s5pESgiwf2YC/2MGDXjAJfpfCd0RpLdvd4eRiXtVlE9qO9bND94E7PgQ/xqZ +J1i2xWFndWa6nfFnRxZmCStCOZWYYPlaxr+FZceFbpMwzTNs4g3d4tLNUcbKAIH4 +0wIDAQAB +-----END PUBLIC KEY-----">>). + + +-type config() :: #{ + api_key => binary() | undefined, + api_organization => binary() | undefined, + api_repository => binary() | undefined, + api_url => binary(), + http_adapter => module(), + http_etag => binary() | undefined, + http_adapter_config => map(), + http_user_agent_fragment => binary(), + repo_key => binary() | undefined, + repo_name => binary(), + repo_public_key => binary(), + repo_url => binary(), + repo_organization => binary() | undefined, + repo_verify => boolean(), + repo_verify_origin => boolean() +}. + +-spec default_config() -> config(). +default_config() -> + #{ + api_key => undefined, + api_organization => undefined, + api_repository => undefined, + api_url => <<"https://hex.pm/api">>, + http_adapter => r3_hex_http_httpc, + http_adapter_config => #{profile => default}, + http_etag => undefined, + http_user_agent_fragment => <<"(httpc)">>, + repo_key => undefined, + repo_name => <<"hexpm">>, + repo_public_key => ?HEXPM_PUBLIC_KEY, + repo_url => <<"https://repo.hex.pm">>, + repo_organization => undefined, + repo_verify => true, + repo_verify_origin => true + }. diff --git a/src/r3_hex_core.hrl b/src/r3_hex_core.hrl new file mode 100644 index 0000000..31c2bf9 --- /dev/null +++ b/src/r3_hex_core.hrl @@ -0,0 +1,3 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-define(HEX_CORE_VERSION, "0.5.0"). diff --git a/src/r3_hex_erl_tar.erl b/src/r3_hex_erl_tar.erl new file mode 100644 index 0000000..a698e43 --- /dev/null +++ b/src/r3_hex_erl_tar.erl @@ -0,0 +1,1966 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @private +%% Copied from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/erl_tar.erl +%% with modifications: +%% - Change module name to `r3_hex_erl_tar` +%% - Set tar mtimes to 0 and remove dependency on :os.system_time/1 +%% - Preserve modes when building tarball +%% - Do not crash if failing to write tar +%% - Allow setting file_info opts on :r3_hex_erl_tar.add + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% This module implements extraction/creation of tar archives. +%% It supports reading most common tar formats, namely V7, STAR, +%% USTAR, GNU, BSD/libarchive, and PAX. It produces archives in USTAR +%% format, unless it must use PAX headers, in which case it produces PAX +%% format. +%% +%% The following references where used: +%% http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5 +%% http://www.gnu.org/software/tar/manual/html_node/Standard.html +%% http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html +-module(r3_hex_erl_tar). + +-export([init/3, + create/2, create/3, + extract/1, extract/2, + table/1, table/2, t/1, tt/1, + open/2, close/1, + add/3, add/4, add/5, + format_error/1]). + +-include_lib("kernel/include/file.hrl"). +-include_lib("r3_hex_erl_tar.hrl"). + +%% Converts the short error reason to a descriptive string. +-spec format_error(term()) -> string(). +format_error(invalid_tar_checksum) -> + "Checksum failed"; +format_error(bad_header) -> + "Unrecognized tar header format"; +format_error({bad_header, Reason}) -> + lists:flatten(io_lib:format("Unrecognized tar header format: ~p", [Reason])); +format_error({invalid_header, negative_size}) -> + "Invalid header: negative size"; +format_error(invalid_sparse_header_size) -> + "Invalid sparse header: negative size"; +format_error(invalid_sparse_map_entry) -> + "Invalid sparse map entry"; +format_error({invalid_sparse_map_entry, Reason}) -> + lists:flatten(io_lib:format("Invalid sparse map entry: ~p", [Reason])); +format_error(invalid_end_of_archive) -> + "Invalid end of archive"; +format_error(eof) -> + "Unexpected end of file"; +format_error(integer_overflow) -> + "Failed to parse numeric: integer overflow"; +format_error({misaligned_read, Pos}) -> + lists:flatten(io_lib:format("Read a block which was misaligned: block_size=~p pos=~p", + [?BLOCK_SIZE, Pos])); +format_error(invalid_gnu_1_0_sparsemap) -> + "Invalid GNU sparse map (version 1.0)"; +format_error({invalid_gnu_0_1_sparsemap, Format}) -> + lists:flatten(io_lib:format("Invalid GNU sparse map (version ~s)", [Format])); +format_error(unsafe_path) -> + "The path points above the current working directory"; +format_error({Name,Reason}) -> + lists:flatten(io_lib:format("~ts: ~ts", [Name,format_error(Reason)])); +format_error(Atom) when is_atom(Atom) -> + file:format_error(Atom); +format_error(Term) -> + lists:flatten(io_lib:format("~tp", [Term])). + +%% Initializes a new reader given a custom file handle and I/O wrappers +-spec init(handle(), write | read, file_op()) -> {ok, reader()} | {error, badarg}. +init(Handle, AccessMode, Fun) when is_function(Fun, 2) -> + Reader = #reader{handle=Handle,access=AccessMode,func=Fun}, + {ok, Pos, Reader2} = do_position(Reader, {cur, 0}), + {ok, Reader2#reader{pos=Pos}}; +init(_Handle, _AccessMode, _Fun) -> + {error, badarg}. + +%%%================================================================ +%% Extracts all files from the tar file Name. +-spec extract(open_handle()) -> ok | {error, term()}. +extract(Name) -> + extract(Name, []). + +%% Extracts (all) files from the tar file Name. +%% Options accepted: +%% - cooked: Opens the tar file without mode `raw` +%% - compressed: Uncompresses the tar file when reading +%% - memory: Returns the tar contents as a list of tuples {Name, Bin} +%% - keep_old_files: Extracted files will not overwrite the destination +%% - {files, ListOfFilesToExtract}: Only extract ListOfFilesToExtract +%% - verbose: Prints verbose information about the extraction, +%% - {cwd, AbsoluteDir}: Sets the current working directory for the extraction +-spec extract(open_handle(), [extract_opt()]) -> + ok + | {ok, [{string(), binary()}]} + | {error, term()}. +extract({binary, Bin}, Opts) when is_list(Opts) -> + do_extract({binary, Bin}, Opts); +extract({file, Fd}, Opts) when is_list(Opts) -> + do_extract({file, Fd}, Opts); +extract(#reader{}=Reader, Opts) when is_list(Opts) -> + do_extract(Reader, Opts); +extract(Name, Opts) when is_list(Name); is_binary(Name), is_list(Opts) -> + do_extract(Name, Opts). + +do_extract(Handle, Opts) when is_list(Opts) -> + Opts2 = extract_opts(Opts), + Acc = if Opts2#read_opts.output =:= memory -> []; true -> ok end, + foldl_read(Handle, fun extract1/4, Acc, Opts2). + +extract1(eof, Reader, _, Acc) when is_list(Acc) -> + {ok, {ok, lists:reverse(Acc)}, Reader}; +extract1(eof, Reader, _, leading_slash) -> + error_logger:info_msg("erl_tar: removed leading '/' from member names\n"), + {ok, ok, Reader}; +extract1(eof, Reader, _, Acc) -> + {ok, Acc, Reader}; +extract1(#tar_header{name=Name,size=Size}=Header, Reader0, Opts, Acc0) -> + case check_extract(Name, Opts) of + true -> + case do_read(Reader0, Size) of + {ok, Bin, Reader1} -> + Acc = extract2(Header, Bin, Opts, Acc0), + {ok, Acc, Reader1}; + {error, _} = Err -> + throw(Err) + end; + false -> + {ok, Acc0, skip_file(Reader0)} + end. + +extract2(Header, Bin, Opts, Acc) -> + case write_extracted_element(Header, Bin, Opts) of + ok -> + case Header of + #tar_header{name="/"++_} -> + leading_slash; + #tar_header{} -> + Acc + end; + {ok, NameBin} when is_list(Acc) -> + [NameBin | Acc]; + {error, _} = Err -> + throw(Err) + end. + +%% Checks if the file Name should be extracted. +check_extract(_, #read_opts{files=all}) -> + true; +check_extract(Name, #read_opts{files=Files}) -> + ordsets:is_element(Name, Files). + +%%%================================================================ +%% The following table functions produce a list of information about +%% the files contained in the archive. +-type filename() :: string(). +-type typeflag() :: regular | link | symlink | + char | block | directory | + fifo | reserved | unknown. +-type mode() :: non_neg_integer(). +-type uid() :: non_neg_integer(). +-type gid() :: non_neg_integer(). + +-type tar_entry() :: {filename(), + typeflag(), + non_neg_integer(), + tar_time(), + mode(), + uid(), + gid()}. + +%% Returns a list of names of the files in the tar file Name. +-spec table(open_handle()) -> {ok, [string()]} | {error, term()}. +table(Name) -> + table(Name, []). + +%% Returns a list of names of the files in the tar file Name. +%% Options accepted: compressed, verbose, cooked. +-spec table(open_handle(), [compressed | verbose | cooked]) -> + {ok, [tar_entry()]} | {error, term()}. +table(Name, Opts) when is_list(Opts) -> + foldl_read(Name, fun table1/4, [], table_opts(Opts)). + +table1(eof, Reader, _, Result) -> + {ok, {ok, lists:reverse(Result)}, Reader}; +table1(#tar_header{}=Header, Reader, #read_opts{verbose=Verbose}, Result) -> + Attrs = table1_attrs(Header, Verbose), + Reader2 = skip_file(Reader), + {ok, [Attrs|Result], Reader2}. + +%% Extracts attributes relevant to table1's output +table1_attrs(#tar_header{typeflag=Typeflag,mode=Mode}=Header, true) -> + Type = typeflag(Typeflag), + Name = Header#tar_header.name, + Mtime = Header#tar_header.mtime, + Uid = Header#tar_header.uid, + Gid = Header#tar_header.gid, + Size = Header#tar_header.size, + {Name, Type, Size, Mtime, Mode, Uid, Gid}; +table1_attrs(#tar_header{name=Name}, _Verbose) -> + Name. + +typeflag(?TYPE_REGULAR) -> regular; +typeflag(?TYPE_REGULAR_A) -> regular; +typeflag(?TYPE_GNU_SPARSE) -> regular; +typeflag(?TYPE_CONT) -> regular; +typeflag(?TYPE_LINK) -> link; +typeflag(?TYPE_SYMLINK) -> symlink; +typeflag(?TYPE_CHAR) -> char; +typeflag(?TYPE_BLOCK) -> block; +typeflag(?TYPE_DIR) -> directory; +typeflag(?TYPE_FIFO) -> fifo; +typeflag(_) -> unknown. + +%%%================================================================ +%% Comments for printing the contents of a tape archive, +%% meant to be invoked from the shell. + +%% Prints each filename in the archive +-spec t(file:filename()) -> ok | {error, term()}. +t(Name) when is_list(Name); is_binary(Name) -> + case table(Name) of + {ok, List} -> + lists:foreach(fun(N) -> ok = io:format("~ts\n", [N]) end, List); + Error -> + Error + end. + +%% Prints verbose information about each file in the archive +-spec tt(open_handle()) -> ok | {error, term()}. +tt(Name) -> + case table(Name, [verbose]) of + {ok, List} -> + lists:foreach(fun print_header/1, List); + Error -> + Error + end. + +%% Used by tt/1 to print a tar_entry tuple +-spec print_header(tar_entry()) -> ok. +print_header({Name, Type, Size, Mtime, Mode, Uid, Gid}) -> + io:format("~s~s ~4w/~-4w ~7w ~s ~s\n", + [type_to_string(Type), mode_to_string(Mode), + Uid, Gid, Size, time_to_string(Mtime), Name]). + +type_to_string(regular) -> "-"; +type_to_string(directory) -> "d"; +type_to_string(link) -> "l"; +type_to_string(symlink) -> "s"; +type_to_string(char) -> "c"; +type_to_string(block) -> "b"; +type_to_string(fifo) -> "f"; +type_to_string(unknown) -> "?". + +%% Converts a numeric mode to its human-readable representation +mode_to_string(Mode) -> + mode_to_string(Mode, "xwrxwrxwr", []). +mode_to_string(Mode, [C|T], Acc) when Mode band 1 =:= 1 -> + mode_to_string(Mode bsr 1, T, [C|Acc]); +mode_to_string(Mode, [_|T], Acc) -> + mode_to_string(Mode bsr 1, T, [$-|Acc]); +mode_to_string(_, [], Acc) -> + Acc. + +%% Converts a tar_time() (POSIX time) to a readable string +time_to_string(Secs0) -> + Epoch = calendar:datetime_to_gregorian_seconds(?EPOCH), + Secs = Epoch + Secs0, + DateTime0 = calendar:gregorian_seconds_to_datetime(Secs), + DateTime = calendar:universal_time_to_local_time(DateTime0), + {{Y, Mon, Day}, {H, Min, _}} = DateTime, + io_lib:format("~s ~2w ~s:~s ~w", [month(Mon), Day, two_d(H), two_d(Min), Y]). + +two_d(N) -> + tl(integer_to_list(N + 100)). + +month(1) -> "Jan"; +month(2) -> "Feb"; +month(3) -> "Mar"; +month(4) -> "Apr"; +month(5) -> "May"; +month(6) -> "Jun"; +month(7) -> "Jul"; +month(8) -> "Aug"; +month(9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +%%%================================================================ +%% The open function with friends is to keep the file and binary api of this module +-type open_handle() :: file:filename() + | {binary, binary()} + | {file, term()}. +-spec open(open_handle(), [write | compressed | cooked]) -> + {ok, reader()} | {error, term()}. +open({binary, Bin}, Mode) when is_binary(Bin) -> + do_open({binary, Bin}, Mode); +open({file, Fd}, Mode) -> + do_open({file, Fd}, Mode); +open(Name, Mode) when is_list(Name); is_binary(Name) -> + do_open(Name, Mode). + +do_open(Name, Mode) when is_list(Mode) -> + case open_mode(Mode) of + {ok, Access, Raw, Opts} -> + open1(Name, Access, Raw, Opts); + {error, Reason} -> + {error, {Name, Reason}} + end. + +open1({binary,Bin}, read, _Raw, Opts) when is_binary(Bin) -> + case file:open(Bin, [ram,binary,read]) of + {ok,File} -> + _ = [ram_file:uncompress(File) || Opts =:= [compressed]], + {ok, #reader{handle=File,access=read,func=fun file_op/2}}; + Error -> + Error + end; +open1({file, Fd}, read, _Raw, _Opts) -> + Reader = #reader{handle=Fd,access=read,func=fun file_op/2}, + case do_position(Reader, {cur, 0}) of + {ok, Pos, Reader2} -> + {ok, Reader2#reader{pos=Pos}}; + {error, _} = Err -> + Err + end; +open1(Name, Access, Raw, Opts) when is_list(Name) or is_binary(Name) -> + case file:open(Name, Raw ++ [binary, Access|Opts]) of + {ok, File} -> + {ok, #reader{handle=File,access=Access,func=fun file_op/2}}; + {error, Reason} -> + {error, {Name, Reason}} + end. + +open_mode(Mode) -> + open_mode(Mode, false, [raw], []). + +open_mode(read, _, Raw, _) -> + {ok, read, Raw, []}; +open_mode(write, _, Raw, _) -> + {ok, write, Raw, []}; +open_mode([read|Rest], false, Raw, Opts) -> + open_mode(Rest, read, Raw, Opts); +open_mode([write|Rest], false, Raw, Opts) -> + open_mode(Rest, write, Raw, Opts); +open_mode([compressed|Rest], Access, Raw, Opts) -> + open_mode(Rest, Access, Raw, [compressed|Opts]); +open_mode([cooked|Rest], Access, _Raw, Opts) -> + open_mode(Rest, Access, [], Opts); +open_mode([], Access, Raw, Opts) -> + {ok, Access, Raw, Opts}; +open_mode(_, _, _, _) -> + {error, einval}. + +file_op(write, {Fd, Data}) -> + file:write(Fd, Data); +file_op(position, {Fd, Pos}) -> + file:position(Fd, Pos); +file_op(read2, {Fd, Size}) -> + file:read(Fd, Size); +file_op(close, Fd) -> + file:close(Fd). + +%% Closes a tar archive. +-spec close(reader()) -> ok | {error, term()}. +close(#reader{access=read}=Reader) -> + ok = do_close(Reader); +close(#reader{access=write}=Reader) -> + {ok, Reader2} = pad_file(Reader), + ok = do_close(Reader2), + ok; +close(_) -> + {error, einval}. + +pad_file(#reader{pos=Pos}=Reader) -> + %% There must be at least two zero blocks at the end. + PadCurrent = skip_padding(Pos+?BLOCK_SIZE), + Padding = <<0:PadCurrent/unit:8>>, + do_write(Reader, [Padding, ?ZERO_BLOCK, ?ZERO_BLOCK]). + + +%%%================================================================ +%% Creation/modification of tar archives + +%% Creates a tar file Name containing the given files. +-spec create(file:filename(), filelist()) -> ok | {error, {string(), term()}}. +create(Name, FileList) when is_list(Name); is_binary(Name) -> + create(Name, FileList, []). + +%% Creates a tar archive Name containing the given files. +%% Accepted options: verbose, compressed, cooked +-spec create(file:filename(), filelist(), [create_opt()]) -> + ok | {error, term()} | {error, {string(), term()}}. +create(Name, FileList, Options) when is_list(Name); is_binary(Name) -> + Mode = lists:filter(fun(X) -> (X=:=compressed) or (X=:=cooked) + end, Options), + case open(Name, [write|Mode]) of + {ok, TarFile} -> + do_create(TarFile, FileList, Options); + {error, _} = Err -> + Err + end. + +do_create(TarFile, [], _Opts) -> + close(TarFile); +do_create(TarFile, [{NameInArchive, NameOrBin}|Rest], Opts) -> + case add(TarFile, NameOrBin, NameInArchive, Opts) of + ok -> + do_create(TarFile, Rest, Opts); + {error, _} = Err -> + _ = close(TarFile), + Err + end; +do_create(TarFile, [Name|Rest], Opts) -> + case add(TarFile, Name, Name, Opts) of + ok -> + do_create(TarFile, Rest, Opts); + {error, _} = Err -> + _ = close(TarFile), + Err + end. + +%% Adds a file to a tape archive. +-type add_type() :: string() + | {string(), string()} + | {string(), binary()}. +-spec add(reader(), add_type(), [add_opt()]) -> ok | {error, term()}. +add(Reader, {NameInArchive, Name}, Opts) + when is_list(NameInArchive), is_list(Name) -> + do_add(Reader, Name, NameInArchive, undefined, Opts); +add(Reader, {NameInArchive, Bin}, Opts) + when is_list(NameInArchive), is_binary(Bin) -> + do_add(Reader, Bin, NameInArchive, undefined, Opts); +add(Reader, {NameInArchive, Bin, Mode}, Opts) + when is_list(NameInArchive), is_binary(Bin), is_integer(Mode) -> + do_add(Reader, Bin, NameInArchive, Mode, Opts); +add(Reader, Name, Opts) when is_list(Name) -> + do_add(Reader, Name, Name, undefined, Opts). + + +-spec add(reader(), string() | binary(), string(), [add_opt()]) -> + ok | {error, term()}. +add(Reader, NameOrBin, NameInArchive, Options) + when is_list(NameOrBin); is_binary(NameOrBin), + is_list(NameInArchive), is_list(Options) -> + do_add(Reader, NameOrBin, NameInArchive, undefined, Options). + +-spec add(reader(), string() | binary(), string(), integer(), [add_opt()]) -> + ok | {error, term()}. +add(Reader, NameOrBin, NameInArchive, Mode, Options) + when is_list(NameOrBin); is_binary(NameOrBin), + is_list(NameInArchive), is_integer(Mode), is_list(Options) -> + do_add(Reader, NameOrBin, NameInArchive, Mode, Options). + +do_add(#reader{access=write}=Reader, Name, NameInArchive, Mode, Options) + when is_list(NameInArchive), is_list(Options) -> + RF = fun(F) -> apply_file_info_opts(Options, file:read_link_info(F, [{time, posix}])) end, + Opts = #add_opts{read_info=RF}, + add1(Reader, Name, NameInArchive, Mode, add_opts(Options, Options, Opts)); +do_add(#reader{access=read},_,_,_,_) -> + {error, eacces}; +do_add(Reader,_,_,_,_) -> + {error, {badarg, Reader}}. + +add_opts([dereference|T], AllOptions, Opts) -> + RF = fun(F) -> apply_file_info_opts(AllOptions, file:read_file_info(F, [{time, posix}])) end, + add_opts(T, AllOptions, Opts#add_opts{read_info=RF}); +add_opts([verbose|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{verbose=true}); +add_opts([{chunks,N}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{chunk_size=N}); +add_opts([{atime,Value}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{atime=Value}); +add_opts([{mtime,Value}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{mtime=Value}); +add_opts([{ctime,Value}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{ctime=Value}); +add_opts([{uid,Value}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{uid=Value}); +add_opts([{gid,Value}|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts#add_opts{gid=Value}); +add_opts([_|T], AllOptions, Opts) -> + add_opts(T, AllOptions, Opts); +add_opts([], _AllOptions, Opts) -> + Opts. + +apply_file_info_opts(Opts, {ok, FileInfo}) -> + {ok, do_apply_file_info_opts(Opts, FileInfo)}; +apply_file_info_opts(_Opts, Other) -> + Other. + +do_apply_file_info_opts([{atime,Value}|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo#file_info{atime=Value}); +do_apply_file_info_opts([{mtime,Value}|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo#file_info{mtime=Value}); +do_apply_file_info_opts([{ctime,Value}|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo#file_info{ctime=Value}); +do_apply_file_info_opts([{uid,Value}|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo#file_info{uid=Value}); +do_apply_file_info_opts([{gid,Value}|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo#file_info{gid=Value}); +do_apply_file_info_opts([_|T], FileInfo) -> + do_apply_file_info_opts(T, FileInfo); +do_apply_file_info_opts([], FileInfo) -> + FileInfo. + +add1(#reader{}=Reader, Name, NameInArchive, undefined, #add_opts{read_info=ReadInfo}=Opts) + when is_list(Name) -> + Res = case ReadInfo(Name) of + {error, Reason0} -> + {error, {Name, Reason0}}; + {ok, #file_info{type=symlink}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + {ok, Linkname} = file:read_link(Name), + Header = fileinfo_to_header(NameInArchive, Fi, Linkname), + add_header(Reader, Header, Opts); + {ok, #file_info{type=regular}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Header = fileinfo_to_header(NameInArchive, Fi, false), + {ok, Reader2} = add_header(Reader, Header, Opts), + FileSize = Header#tar_header.size, + {ok, FileSize, Reader3} = do_copy(Reader2, Name, Opts), + Padding = skip_padding(FileSize), + Pad = <<0:Padding/unit:8>>, + do_write(Reader3, Pad); + {ok, #file_info{type=directory}=Fi} -> + add_directory(Reader, Name, NameInArchive, Fi, Opts); + {ok, #file_info{}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Header = fileinfo_to_header(NameInArchive, Fi, false), + add_header(Reader, Header, Opts) + end, + case Res of + ok -> ok; + {ok, _Reader} -> ok; + {error, _Reason} = Err -> Err + end; +add1(Reader, Bin, NameInArchive, Mode, Opts) when is_binary(Bin) -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Now = 0, + Header = #tar_header{ + name = NameInArchive, + size = byte_size(Bin), + typeflag = ?TYPE_REGULAR, + atime = add_opts_time(Opts#add_opts.atime, Now), + mtime = add_opts_time(Opts#add_opts.mtime, Now), + ctime = add_opts_time(Opts#add_opts.ctime, Now), + uid = Opts#add_opts.uid, + gid = Opts#add_opts.gid, + mode = default_mode(Mode, 8#100644)}, + {ok, Reader2} = add_header(Reader, Header, Opts), + Padding = skip_padding(byte_size(Bin)), + Data = [Bin, <<0:Padding/unit:8>>], + case do_write(Reader2, Data) of + {ok, _Reader3} -> ok; + {error, Reason} -> {error, {NameInArchive, Reason}} + end. + +add_opts_time(undefined, _Now) -> 0; +add_opts_time(Time, _Now) -> Time. + +default_mode(undefined, Mode) -> Mode; +default_mode(Mode, _) -> Mode. + +add_directory(Reader, DirName, NameInArchive, Info, Opts) -> + case file:list_dir(DirName) of + {ok, []} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Header = fileinfo_to_header(NameInArchive, Info, false), + add_header(Reader, Header, Opts); + {ok, Files} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + try add_files(Reader, Files, DirName, NameInArchive, Opts) of + ok -> ok; + {error, _} = Err -> Err + catch + throw:{error, {_Name, _Reason}} = Err -> Err; + throw:{error, Reason} -> {error, {DirName, Reason}} + end; + {error, Reason} -> + {error, {DirName, Reason}} + end. + +add_files(_Reader, [], _Dir, _DirInArchive, _Opts) -> + ok; +add_files(Reader, [Name|Rest], Dir, DirInArchive, #add_opts{read_info=Info}=Opts) -> + FullName = filename:join(Dir, Name), + NameInArchive = filename:join(DirInArchive, Name), + Res = case Info(FullName) of + {error, Reason} -> + {error, {FullName, Reason}}; + {ok, #file_info{type=directory}=Fi} -> + add_directory(Reader, FullName, NameInArchive, Fi, Opts); + {ok, #file_info{type=symlink}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + {ok, Linkname} = file:read_link(FullName), + Header = fileinfo_to_header(NameInArchive, Fi, Linkname), + add_header(Reader, Header, Opts); + {ok, #file_info{type=regular}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Header = fileinfo_to_header(NameInArchive, Fi, false), + {ok, Reader2} = add_header(Reader, Header, Opts), + FileSize = Header#tar_header.size, + {ok, FileSize, Reader3} = do_copy(Reader2, FullName, Opts), + Padding = skip_padding(FileSize), + Pad = <<0:Padding/unit:8>>, + do_write(Reader3, Pad); + {ok, #file_info{}=Fi} -> + add_verbose(Opts, "a ~ts~n", [NameInArchive]), + Header = fileinfo_to_header(NameInArchive, Fi, false), + add_header(Reader, Header, Opts) + end, + case Res of + ok -> add_files(Reader, Rest, Dir, DirInArchive, Opts); + {ok, ReaderNext} -> add_files(ReaderNext, Rest, Dir, DirInArchive, Opts); + {error, _} = Err -> Err + end. + +format_string(String, Size) when length(String) > Size -> + throw({error, {write_string, field_too_long}}); +format_string(String, Size) -> + Ascii = to_ascii(String), + if byte_size(Ascii) < Size -> + [Ascii, 0]; + true -> + Ascii + end. + +format_octal(Octal) -> + iolist_to_binary(io_lib:fwrite("~.8B", [Octal])). + +add_header(#reader{}=Reader, #tar_header{}=Header, Opts) -> + {ok, Iodata} = build_header(Header, Opts), + do_write(Reader, Iodata). + +write_to_block(Block, IoData, Start) when is_list(IoData) -> + write_to_block(Block, iolist_to_binary(IoData), Start); +write_to_block(Block, Bin, Start) when is_binary(Bin) -> + Size = byte_size(Bin), + <<Head:Start/unit:8, _:Size/unit:8, Rest/binary>> = Block, + <<Head:Start/unit:8, Bin/binary, Rest/binary>>. + +build_header(#tar_header{}=Header, Opts) -> + #tar_header{ + name=Name, + mode=Mode, + uid=Uid, + gid=Gid, + size=Size, + typeflag=Type, + linkname=Linkname, + uname=Uname, + gname=Gname, + devmajor=Devmaj, + devminor=Devmin + } = Header, + Mtime = Header#tar_header.mtime, + + Block0 = ?ZERO_BLOCK, + {Block1, Pax0} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, Name, ?PAX_PATH, #{}), + Block2 = write_octal(Block1, ?V7_MODE, ?V7_MODE_LEN, Mode), + {Block3, Pax1} = write_numeric(Block2, ?V7_UID, ?V7_UID_LEN, Uid, ?PAX_UID, Pax0), + {Block4, Pax2} = write_numeric(Block3, ?V7_GID, ?V7_GID_LEN, Gid, ?PAX_GID, Pax1), + {Block5, Pax3} = write_numeric(Block4, ?V7_SIZE, ?V7_SIZE_LEN, Size, ?PAX_SIZE, Pax2), + {Block6, Pax4} = write_numeric(Block5, ?V7_MTIME, ?V7_MTIME_LEN, Mtime, ?PAX_NONE, Pax3), + {Block7, Pax5} = write_string(Block6, ?V7_TYPE, ?V7_TYPE_LEN, <<Type>>, ?PAX_NONE, Pax4), + {Block8, Pax6} = write_string(Block7, ?V7_LINKNAME, ?V7_LINKNAME_LEN, + Linkname, ?PAX_LINKPATH, Pax5), + {Block9, Pax7} = write_string(Block8, ?USTAR_UNAME, ?USTAR_UNAME_LEN, + Uname, ?PAX_UNAME, Pax6), + {Block10, Pax8} = write_string(Block9, ?USTAR_GNAME, ?USTAR_GNAME_LEN, + Gname, ?PAX_GNAME, Pax7), + {Block11, Pax9} = write_numeric(Block10, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN, + Devmaj, ?PAX_NONE, Pax8), + {Block12, Pax10} = write_numeric(Block11, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN, + Devmin, ?PAX_NONE, Pax9), + {Block13, Pax11} = set_path(Block12, Pax10), + PaxEntry = case maps:size(Pax11) of + 0 -> []; + _ -> build_pax_entry(Header, Pax11, Opts) + end, + Block14 = set_format(Block13, ?FORMAT_USTAR), + Block15 = set_checksum(Block14), + {ok, [PaxEntry, Block15]}. + +set_path(Block0, Pax) -> + %% only use ustar header when name is too long + case maps:get(?PAX_PATH, Pax, nil) of + nil -> + {Block0, Pax}; + PaxPath -> + case split_ustar_path(PaxPath) of + {ok, UstarName, UstarPrefix} -> + {Block1, _} = write_string(Block0, ?V7_NAME, ?V7_NAME_LEN, + UstarName, ?PAX_NONE, #{}), + {Block2, _} = write_string(Block1, ?USTAR_PREFIX, ?USTAR_PREFIX_LEN, + UstarPrefix, ?PAX_NONE, #{}), + {Block2, maps:remove(?PAX_PATH, Pax)}; + false -> + {Block0, Pax} + end + end. + +set_format(Block0, Format) + when Format =:= ?FORMAT_USTAR; Format =:= ?FORMAT_PAX -> + Block1 = write_to_block(Block0, ?MAGIC_USTAR, ?USTAR_MAGIC), + write_to_block(Block1, ?VERSION_USTAR, ?USTAR_VERSION); +set_format(_Block, Format) -> + throw({error, {invalid_format, Format}}). + +set_checksum(Block) -> + Checksum = compute_checksum(Block), + write_octal(Block, ?V7_CHKSUM, ?V7_CHKSUM_LEN, Checksum). + +build_pax_entry(Header, PaxAttrs, Opts) -> + Path = Header#tar_header.name, + Filename = filename:basename(Path), + Dir = filename:dirname(Path), + Path2 = filename:join([Dir, "PaxHeaders.0", Filename]), + AsciiPath = to_ascii(Path2), + Path3 = if byte_size(AsciiPath) > ?V7_NAME_LEN -> + binary_part(AsciiPath, 0, ?V7_NAME_LEN - 1); + true -> + AsciiPath + end, + Keys = maps:keys(PaxAttrs), + SortedKeys = lists:sort(Keys), + PaxFile = build_pax_file(SortedKeys, PaxAttrs), + Size = byte_size(PaxFile), + Padding = (?BLOCK_SIZE - + (byte_size(PaxFile) rem ?BLOCK_SIZE)) rem ?BLOCK_SIZE, + Pad = <<0:Padding/unit:8>>, + PaxHeader = #tar_header{ + name=unicode:characters_to_list(Path3), + size=Size, + mtime=Header#tar_header.mtime, + atime=Header#tar_header.atime, + ctime=Header#tar_header.ctime, + typeflag=?TYPE_X_HEADER + }, + {ok, PaxHeaderData} = build_header(PaxHeader, Opts), + [PaxHeaderData, PaxFile, Pad]. + +build_pax_file(Keys, PaxAttrs) -> + build_pax_file(Keys, PaxAttrs, []). +build_pax_file([], _, Acc) -> + unicode:characters_to_binary(Acc); +build_pax_file([K|Rest], Attrs, Acc) -> + V = maps:get(K, Attrs), + Size = sizeof(K) + sizeof(V) + 3, + Size2 = sizeof(Size) + Size, + Key = to_string(K), + Value = to_string(V), + Record = unicode:characters_to_binary(io_lib:format("~B ~ts=~ts\n", [Size2, Key, Value])), + if byte_size(Record) =/= Size2 -> + Size3 = byte_size(Record), + Record2 = io_lib:format("~B ~ts=~ts\n", [Size3, Key, Value]), + build_pax_file(Rest, Attrs, [Acc, Record2]); + true -> + build_pax_file(Rest, Attrs, [Acc, Record]) + end. + +sizeof(Bin) when is_binary(Bin) -> + byte_size(Bin); +sizeof(List) when is_list(List) -> + length(List); +sizeof(N) when is_integer(N) -> + byte_size(integer_to_binary(N)); +sizeof(N) when is_float(N) -> + byte_size(float_to_binary(N)). + +to_string(Bin) when is_binary(Bin) -> + unicode:characters_to_list(Bin); +to_string(List) when is_list(List) -> + List; +to_string(N) when is_integer(N) -> + integer_to_list(N); +to_string(N) when is_float(N) -> + float_to_list(N). + +split_ustar_path(Path) -> + Len = length(Path), + NotAscii = not is_ascii(Path), + if Len =< ?V7_NAME_LEN; NotAscii -> + false; + true -> + PathBin = binary:list_to_bin(Path), + case binary:split(PathBin, [<<$/>>], [global, trim_all]) of + [Part] when byte_size(Part) >= ?V7_NAME_LEN -> + false; + Parts -> + case lists:last(Parts) of + Name when byte_size(Name) >= ?V7_NAME_LEN -> + false; + Name -> + Parts2 = lists:sublist(Parts, length(Parts) - 1), + join_split_ustar_path(Parts2, {ok, Name, nil}) + end + end + end. + +join_split_ustar_path([], Acc) -> + Acc; +join_split_ustar_path([Part|_], {ok, _, nil}) + when byte_size(Part) > ?USTAR_PREFIX_LEN -> + false; +join_split_ustar_path([Part|_], {ok, _Name, Acc}) + when (byte_size(Part)+byte_size(Acc)) > ?USTAR_PREFIX_LEN -> + false; +join_split_ustar_path([Part|Rest], {ok, Name, nil}) -> + join_split_ustar_path(Rest, {ok, Name, Part}); +join_split_ustar_path([Part|Rest], {ok, Name, Acc}) -> + join_split_ustar_path(Rest, {ok, Name, <<Acc/binary,$/,Part/binary>>}). + +write_octal(Block, Pos, Size, X) -> + Octal = zero_pad(format_octal(X), Size-1), + if byte_size(Octal) < Size -> + write_to_block(Block, Octal, Pos); + true -> + throw({error, {write_failed, octal_field_too_long}}) + end. + +write_string(Block, Pos, Size, Str, PaxAttr, Pax0) -> + NotAscii = not is_ascii(Str), + if PaxAttr =/= ?PAX_NONE andalso (length(Str) > Size orelse NotAscii) -> + Pax1 = maps:put(PaxAttr, Str, Pax0), + {Block, Pax1}; + true -> + Formatted = format_string(Str, Size), + {write_to_block(Block, Formatted, Pos), Pax0} + end. +write_numeric(Block, Pos, Size, X, PaxAttr, Pax0) -> + %% attempt octal + Octal = zero_pad(format_octal(X), Size-1), + if byte_size(Octal) < Size -> + {write_to_block(Block, [Octal, 0], Pos), Pax0}; + PaxAttr =/= ?PAX_NONE -> + Pax1 = maps:put(PaxAttr, X, Pax0), + {Block, Pax1}; + true -> + throw({error, {write_failed, numeric_field_too_long}}) + end. + +zero_pad(Str, Size) when byte_size(Str) >= Size -> + Str; +zero_pad(Str, Size) -> + Padding = Size - byte_size(Str), + Pad = binary:copy(<<$0>>, Padding), + <<Pad/binary, Str/binary>>. + + +%%%================================================================ +%% Functions for creating or modifying tar archives + +read_block(Reader) -> + case do_read(Reader, ?BLOCK_SIZE) of + eof -> + throw({error, eof}); + %% Two zero blocks mark the end of the archive + {ok, ?ZERO_BLOCK, Reader1} -> + case do_read(Reader1, ?BLOCK_SIZE) of + eof -> + % This is technically a malformed end-of-archive marker, + % as two ZERO_BLOCKs are expected as the marker, + % but if we've already made it this far, we should just ignore it + eof; + {ok, ?ZERO_BLOCK, _Reader2} -> + eof; + {ok, _Block, _Reader2} -> + throw({error, invalid_end_of_archive}); + {error,_} = Err -> + throw(Err) + end; + {ok, Block, Reader1} when is_binary(Block) -> + {ok, Block, Reader1}; + {error, _} = Err -> + throw(Err) + end. + +get_header(#reader{}=Reader) -> + case read_block(Reader) of + eof -> + eof; + {ok, Block, Reader1} -> + convert_header(Block, Reader1) + end. + +%% Converts the tar header to a record. +to_v7(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + #header_v7{ + name=binary_part(Bin, ?V7_NAME, ?V7_NAME_LEN), + mode=binary_part(Bin, ?V7_MODE, ?V7_MODE_LEN), + uid=binary_part(Bin, ?V7_UID, ?V7_UID_LEN), + gid=binary_part(Bin, ?V7_GID, ?V7_GID_LEN), + size=binary_part(Bin, ?V7_SIZE, ?V7_SIZE_LEN), + mtime=binary_part(Bin, ?V7_MTIME, ?V7_MTIME_LEN), + checksum=binary_part(Bin, ?V7_CHKSUM, ?V7_CHKSUM_LEN), + typeflag=binary:at(Bin, ?V7_TYPE), + linkname=binary_part(Bin, ?V7_LINKNAME, ?V7_LINKNAME_LEN) + }; +to_v7(_) -> + {error, header_block_too_small}. + +to_gnu(#header_v7{}=V7, Bin) + when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + #header_gnu{ + header_v7=V7, + magic=binary_part(Bin, ?GNU_MAGIC, ?GNU_MAGIC_LEN), + version=binary_part(Bin, ?GNU_VERSION, ?GNU_VERSION_LEN), + uname=binary_part(Bin, 265, 32), + gname=binary_part(Bin, 297, 32), + devmajor=binary_part(Bin, 329, 8), + devminor=binary_part(Bin, 337, 8), + atime=binary_part(Bin, 345, 12), + ctime=binary_part(Bin, 357, 12), + sparse=to_sparse_array(binary_part(Bin, 386, 24*4+1)), + real_size=binary_part(Bin, 483, 12) + }. + +to_star(#header_v7{}=V7, Bin) + when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + #header_star{ + header_v7=V7, + magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN), + version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN), + uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN), + gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN), + devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN), + devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN), + prefix=binary_part(Bin, 345, 131), + atime=binary_part(Bin, 476, 12), + ctime=binary_part(Bin, 488, 12), + trailer=binary_part(Bin, ?STAR_TRAILER, ?STAR_TRAILER_LEN) + }. + +to_ustar(#header_v7{}=V7, Bin) + when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + #header_ustar{ + header_v7=V7, + magic=binary_part(Bin, ?USTAR_MAGIC, ?USTAR_MAGIC_LEN), + version=binary_part(Bin, ?USTAR_VERSION, ?USTAR_VERSION_LEN), + uname=binary_part(Bin, ?USTAR_UNAME, ?USTAR_UNAME_LEN), + gname=binary_part(Bin, ?USTAR_GNAME, ?USTAR_GNAME_LEN), + devmajor=binary_part(Bin, ?USTAR_DEVMAJ, ?USTAR_DEVMAJ_LEN), + devminor=binary_part(Bin, ?USTAR_DEVMIN, ?USTAR_DEVMIN_LEN), + prefix=binary_part(Bin, 345, 155) + }. + +to_sparse_array(Bin) when is_binary(Bin) -> + MaxEntries = byte_size(Bin) div 24, + IsExtended = 1 =:= binary:at(Bin, 24*MaxEntries), + Entries = parse_sparse_entries(Bin, MaxEntries-1, []), + #sparse_array{ + entries=Entries, + max_entries=MaxEntries, + is_extended=IsExtended + }. + +parse_sparse_entries(<<>>, _, Acc) -> + Acc; +parse_sparse_entries(_, -1, Acc) -> + Acc; +parse_sparse_entries(Bin, N, Acc) -> + case to_sparse_entry(binary_part(Bin, N*24, 24)) of + nil -> + parse_sparse_entries(Bin, N-1, Acc); + Entry = #sparse_entry{} -> + parse_sparse_entries(Bin, N-1, [Entry|Acc]) + end. + +-define(EMPTY_ENTRY, <<0,0,0,0,0,0,0,0,0,0,0,0>>). +to_sparse_entry(Bin) when is_binary(Bin), byte_size(Bin) =:= 24 -> + OffsetBin = binary_part(Bin, 0, 12), + NumBytesBin = binary_part(Bin, 12, 12), + case {OffsetBin, NumBytesBin} of + {?EMPTY_ENTRY, ?EMPTY_ENTRY} -> + nil; + _ -> + #sparse_entry{ + offset=parse_numeric(OffsetBin), + num_bytes=parse_numeric(NumBytesBin)} + end. + +-spec get_format(binary()) -> {ok, pos_integer(), header_v7()} + | ?FORMAT_UNKNOWN + | {error, term()}. +get_format(Bin) when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + do_get_format(to_v7(Bin), Bin). + +do_get_format({error, _} = Err, _Bin) -> + Err; +do_get_format(#header_v7{}=V7, Bin) + when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + Checksum = parse_octal(V7#header_v7.checksum), + Chk1 = compute_checksum(Bin), + Chk2 = compute_signed_checksum(Bin), + if Checksum =/= Chk1 andalso Checksum =/= Chk2 -> + ?FORMAT_UNKNOWN; + true -> + %% guess magic + Ustar = to_ustar(V7, Bin), + Star = to_star(V7, Bin), + Magic = Ustar#header_ustar.magic, + Version = Ustar#header_ustar.version, + Trailer = Star#header_star.trailer, + Format = if + Magic =:= ?MAGIC_USTAR, Trailer =:= ?TRAILER_STAR -> + ?FORMAT_STAR; + Magic =:= ?MAGIC_USTAR -> + ?FORMAT_USTAR; + Magic =:= ?MAGIC_GNU, Version =:= ?VERSION_GNU -> + ?FORMAT_GNU; + true -> + ?FORMAT_V7 + end, + {ok, Format, V7} + end. + +unpack_format(Format, #header_v7{}=V7, Bin, Reader) + when is_binary(Bin), byte_size(Bin) =:= ?BLOCK_SIZE -> + Mtime = parse_numeric(V7#header_v7.mtime), + Header0 = #tar_header{ + name=parse_string(V7#header_v7.name), + mode=parse_numeric(V7#header_v7.mode), + uid=parse_numeric(V7#header_v7.uid), + gid=parse_numeric(V7#header_v7.gid), + size=parse_numeric(V7#header_v7.size), + mtime=Mtime, + atime=Mtime, + ctime=Mtime, + typeflag=V7#header_v7.typeflag, + linkname=parse_string(V7#header_v7.linkname) + }, + Typeflag = Header0#tar_header.typeflag, + Header1 = if Format > ?FORMAT_V7 -> + unpack_modern(Format, V7, Bin, Header0); + true -> + Name = Header0#tar_header.name, + Header0#tar_header{name=safe_join_path("", Name)} + end, + HeaderOnly = is_header_only_type(Typeflag), + Header2 = if HeaderOnly -> + Header1#tar_header{size=0}; + true -> + Header1 + end, + if Typeflag =:= ?TYPE_GNU_SPARSE -> + Gnu = to_gnu(V7, Bin), + RealSize = parse_numeric(Gnu#header_gnu.real_size), + {Sparsemap, Reader2} = parse_sparse_map(Gnu, Reader), + Header3 = Header2#tar_header{size=RealSize}, + {Header3, new_sparse_file_reader(Reader2, Sparsemap, RealSize)}; + true -> + FileReader = #reg_file_reader{ + handle=Reader, + num_bytes=Header2#tar_header.size, + size=Header2#tar_header.size, + pos = 0 + }, + {Header2, FileReader} + end. + +unpack_modern(Format, #header_v7{}=V7, Bin, #tar_header{}=Header0) + when is_binary(Bin) -> + Typeflag = Header0#tar_header.typeflag, + Ustar = to_ustar(V7, Bin), + H0 = Header0#tar_header{ + uname=parse_string(Ustar#header_ustar.uname), + gname=parse_string(Ustar#header_ustar.gname)}, + H1 = if Typeflag =:= ?TYPE_CHAR + orelse Typeflag =:= ?TYPE_BLOCK -> + Ma = parse_numeric(Ustar#header_ustar.devmajor), + Mi = parse_numeric(Ustar#header_ustar.devminor), + H0#tar_header{ + devmajor=Ma, + devminor=Mi + }; + true -> + H0 + end, + {Prefix, H2} = case Format of + ?FORMAT_USTAR -> + {parse_string(Ustar#header_ustar.prefix), H1}; + ?FORMAT_STAR -> + Star = to_star(V7, Bin), + Prefix0 = parse_string(Star#header_star.prefix), + Atime0 = Star#header_star.atime, + Atime = parse_numeric(Atime0), + Ctime0 = Star#header_star.ctime, + Ctime = parse_numeric(Ctime0), + {Prefix0, H1#tar_header{ + atime=Atime, + ctime=Ctime + }}; + _ -> + {"", H1} + end, + Name = H2#tar_header.name, + H2#tar_header{name=safe_join_path(Prefix, Name)}. + + +safe_join_path([], Name) -> + filename:join([Name]); +safe_join_path(Prefix, []) -> + filename:join([Prefix]); +safe_join_path(Prefix, Name) -> + filename:join(Prefix, Name). + +new_sparse_file_reader(Reader, Sparsemap, RealSize) -> + true = validate_sparse_entries(Sparsemap, RealSize), + #sparse_file_reader{ + handle = Reader, + num_bytes = RealSize, + pos = 0, + size = RealSize, + sparse_map = Sparsemap}. + +validate_sparse_entries(Entries, RealSize) -> + validate_sparse_entries(Entries, RealSize, 0, 0). +validate_sparse_entries([], _RealSize, _I, _LastOffset) -> + true; +validate_sparse_entries([#sparse_entry{}=Entry|Rest], RealSize, I, LastOffset) -> + Offset = Entry#sparse_entry.offset, + NumBytes = Entry#sparse_entry.num_bytes, + if + Offset > ?MAX_INT64-NumBytes -> + throw({error, {invalid_sparse_map_entry, offset_too_large}}); + Offset+NumBytes > RealSize -> + throw({error, {invalid_sparse_map_entry, offset_too_large}}); + I > 0 andalso LastOffset > Offset -> + throw({error, {invalid_sparse_map_entry, overlapping_offsets}}); + true -> + ok + end, + validate_sparse_entries(Rest, RealSize, I+1, Offset+NumBytes). + + +-spec parse_sparse_map(header_gnu(), reader_type()) -> + {[sparse_entry()], reader_type()}. +parse_sparse_map(#header_gnu{sparse=Sparse}, Reader) + when Sparse#sparse_array.is_extended -> + parse_sparse_map(Sparse, Reader, []); +parse_sparse_map(#header_gnu{sparse=Sparse}, Reader) -> + {Sparse#sparse_array.entries, Reader}. +parse_sparse_map(#sparse_array{is_extended=true,entries=Entries}, Reader, Acc) -> + case read_block(Reader) of + eof -> + throw({error, eof}); + {ok, Block, Reader2} -> + Sparse2 = to_sparse_array(Block), + parse_sparse_map(Sparse2, Reader2, Entries++Acc) + end; +parse_sparse_map(#sparse_array{entries=Entries}, Reader, Acc) -> + Sorted = lists:sort(fun (#sparse_entry{offset=A},#sparse_entry{offset=B}) -> + A =< B + end, Entries++Acc), + {Sorted, Reader}. + +%% Defined by taking the sum of the unsigned byte values of the +%% entire header record, treating the checksum bytes to as ASCII spaces +compute_checksum(<<H1:?V7_CHKSUM/binary, + H2:?V7_CHKSUM_LEN/binary, + Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary, + _/binary>>) -> + C0 = checksum(H1) + (byte_size(H2) * $\s), + C1 = checksum(Rest), + C0 + C1. + +compute_signed_checksum(<<H1:?V7_CHKSUM/binary, + H2:?V7_CHKSUM_LEN/binary, + Rest:(?BLOCK_SIZE - ?V7_CHKSUM - ?V7_CHKSUM_LEN)/binary, + _/binary>>) -> + C0 = signed_checksum(H1) + (byte_size(H2) * $\s), + C1 = signed_checksum(Rest), + C0 + C1. + +%% Returns the checksum of a binary. +checksum(Bin) -> checksum(Bin, 0). +checksum(<<A/unsigned,Rest/binary>>, Sum) -> + checksum(Rest, Sum+A); +checksum(<<>>, Sum) -> Sum. + +signed_checksum(Bin) -> signed_checksum(Bin, 0). +signed_checksum(<<A/signed,Rest/binary>>, Sum) -> + signed_checksum(Rest, Sum+A); +signed_checksum(<<>>, Sum) -> Sum. + +-spec parse_numeric(binary()) -> non_neg_integer(). +parse_numeric(<<>>) -> + 0; +parse_numeric(<<First, _/binary>> = Bin) -> + %% check for base-256 format first + %% if the bit is set, then all following bits constitute a two's + %% complement encoded number in big-endian byte order + if + First band 16#80 =/= 0 -> + %% Handling negative numbers relies on the following identity: + %% -a-1 == ^a + %% If the number is negative, we use an inversion mask to invert + %% the data bytes and treat the value as an unsigned number + Inv = if First band 16#40 =/= 0 -> 16#00; true -> 16#FF end, + Bytes = binary:bin_to_list(Bin), + Reducer = fun (C, {I, X}) -> + C1 = C bxor Inv, + C2 = if I =:= 0 -> C1 band 16#7F; true -> C1 end, + if (X bsr 56) > 0 -> + throw({error,integer_overflow}); + true -> + {I+1, (X bsl 8) bor C2} + end + end, + {_, N} = lists:foldl(Reducer, {0,0}, Bytes), + if (N bsr 63) > 0 -> + throw({error, integer_overflow}); + true -> + if Inv =:= 16#FF -> + -1 bxor N; + true -> + N + end + end; + true -> + %% normal case is an octal number + parse_octal(Bin) + end. + +parse_octal(Bin) when is_binary(Bin) -> + %% skip leading/trailing zero bytes and spaces + do_parse_octal(Bin, <<>>). +do_parse_octal(<<>>, <<>>) -> + 0; +do_parse_octal(<<>>, Acc) -> + case io_lib:fread("~8u", binary:bin_to_list(Acc)) of + {error, _} -> throw({error, invalid_tar_checksum}); + {ok, [Octal], []} -> Octal; + {ok, _, _} -> throw({error, invalid_tar_checksum}) + end; +do_parse_octal(<<$\s,Rest/binary>>, Acc) -> + do_parse_octal(Rest, Acc); +do_parse_octal(<<0, Rest/binary>>, Acc) -> + do_parse_octal(Rest, Acc); +do_parse_octal(<<C, Rest/binary>>, Acc) -> + do_parse_octal(Rest, <<Acc/binary, C>>). + +parse_string(Bin) when is_binary(Bin) -> + do_parse_string(Bin, <<>>). +do_parse_string(<<>>, Acc) -> + case unicode:characters_to_list(Acc) of + Str when is_list(Str) -> + Str; + {incomplete, _Str, _Rest} -> + binary:bin_to_list(Acc); + {error, _Str, _Rest} -> + throw({error, {bad_header, invalid_string}}) + end; +do_parse_string(<<0, _/binary>>, Acc) -> + do_parse_string(<<>>, Acc); +do_parse_string(<<C, Rest/binary>>, Acc) -> + do_parse_string(Rest, <<Acc/binary, C>>). + +convert_header(Bin, #reader{pos=Pos}=Reader) + when byte_size(Bin) =:= ?BLOCK_SIZE, (Pos rem ?BLOCK_SIZE) =:= 0 -> + case get_format(Bin) of + ?FORMAT_UNKNOWN -> + throw({error, bad_header}); + {ok, Format, V7} -> + unpack_format(Format, V7, Bin, Reader); + {error, Reason} -> + throw({error, {bad_header, Reason}}) + end; +convert_header(Bin, #reader{pos=Pos}) when byte_size(Bin) =:= ?BLOCK_SIZE -> + throw({error, misaligned_read, Pos}); +convert_header(Bin, _Reader) when byte_size(Bin) =:= 0 -> + eof; +convert_header(_Bin, _Reader) -> + throw({error, eof}). + +%% Creates a partially-populated header record based +%% on the provided file_info record. If the file is +%% a symlink, then `link` is used as the link target. +%% If the file is a directory, a slash is appended to the name. +fileinfo_to_header(Name, #file_info{}=Fi, Link) when is_list(Name) -> + BaseHeader = #tar_header{name=Name, + mtime=0, + atime=0, + ctime=0, + mode=Fi#file_info.mode, + typeflag=?TYPE_REGULAR}, + do_fileinfo_to_header(BaseHeader, Fi, Link). + +do_fileinfo_to_header(Header, #file_info{size=Size,type=regular}, _Link) -> + Header#tar_header{size=Size,typeflag=?TYPE_REGULAR}; +do_fileinfo_to_header(#tar_header{name=Name}=Header, + #file_info{type=directory}, _Link) -> + Header#tar_header{name=Name++"/",typeflag=?TYPE_DIR}; +do_fileinfo_to_header(Header, #file_info{type=symlink}, Link) -> + Header#tar_header{typeflag=?TYPE_SYMLINK,linkname=Link}; +do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link) + when (Mode band ?S_IFMT) =:= ?S_IFCHR -> + Header#tar_header{typeflag=?TYPE_CHAR, + devmajor=Fi#file_info.major_device, + devminor=Fi#file_info.minor_device}; +do_fileinfo_to_header(Header, #file_info{type=device,mode=Mode}=Fi, _Link) + when (Mode band ?S_IFMT) =:= ?S_IFBLK -> + Header#tar_header{typeflag=?TYPE_BLOCK, + devmajor=Fi#file_info.major_device, + devminor=Fi#file_info.minor_device}; +do_fileinfo_to_header(Header, #file_info{type=other,mode=Mode}, _Link) + when (Mode band ?S_IFMT) =:= ?S_FIFO -> + Header#tar_header{typeflag=?TYPE_FIFO}; +do_fileinfo_to_header(Header, Fi, _Link) -> + {error, {invalid_file_type, Header#tar_header.name, Fi}}. + +is_ascii(Str) when is_list(Str) -> + not lists:any(fun (Char) -> Char >= 16#80 end, Str); +is_ascii(Bin) when is_binary(Bin) -> + is_ascii1(Bin). + +is_ascii1(<<>>) -> + true; +is_ascii1(<<C,_Rest/binary>>) when C >= 16#80 -> + false; +is_ascii1(<<_, Rest/binary>>) -> + is_ascii1(Rest). + +to_ascii(Str) when is_list(Str) -> + case is_ascii(Str) of + true -> + unicode:characters_to_binary(Str); + false -> + Chars = lists:filter(fun (Char) -> Char < 16#80 end, Str), + unicode:characters_to_binary(Chars) + end; +to_ascii(Bin) when is_binary(Bin) -> + to_ascii(Bin, <<>>). +to_ascii(<<>>, Acc) -> + Acc; +to_ascii(<<C, Rest/binary>>, Acc) when C < 16#80 -> + to_ascii(Rest, <<Acc/binary,C>>); +to_ascii(<<_, Rest/binary>>, Acc) -> + to_ascii(Rest, Acc). + +is_header_only_type(?TYPE_SYMLINK) -> true; +is_header_only_type(?TYPE_LINK) -> true; +is_header_only_type(?TYPE_DIR) -> true; +is_header_only_type(_) -> false. + +foldl_read(#reader{access=read}=Reader, Fun, Accu, #read_opts{}=Opts) + when is_function(Fun,4) -> + case foldl_read0(Reader, Fun, Accu, Opts) of + {ok, Result, _Reader2} -> + Result; + {error, _} = Err -> + Err + end; +foldl_read(#reader{access=Access}, _Fun, _Accu, _Opts) -> + {error, {read_mode_expected, Access}}; +foldl_read(TarName, Fun, Accu, #read_opts{}=Opts) + when is_function(Fun,4) -> + try open(TarName, [read|Opts#read_opts.open_mode]) of + {ok, #reader{access=read}=Reader} -> + try + foldl_read(Reader, Fun, Accu, Opts) + after + _ = close(Reader) + end; + {error, _} = Err -> + Err + catch + throw:Err -> + Err + end. + +foldl_read0(Reader, Fun, Accu, Opts) -> + try foldl_read1(Fun, Accu, Reader, Opts, #{}) of + {ok,_,_} = Ok -> + Ok + catch + throw:{error, {Reason, Format, Args}} -> + read_verbose(Opts, Format, Args), + {error, Reason}; + throw:Err -> + Err + end. + +foldl_read1(Fun, Accu0, Reader0, Opts, ExtraHeaders) -> + {ok, Reader1} = skip_unread(Reader0), + case get_header(Reader1) of + eof -> + Fun(eof, Reader1, Opts, Accu0); + {Header, Reader2} -> + case Header#tar_header.typeflag of + ?TYPE_X_HEADER -> + {ExtraHeaders2, Reader3} = parse_pax(Reader2), + ExtraHeaders3 = maps:merge(ExtraHeaders, ExtraHeaders2), + foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders3); + ?TYPE_GNU_LONGNAME -> + {RealName, Reader3} = get_real_name(Reader2), + ExtraHeaders2 = maps:put(?PAX_PATH, + parse_string(RealName), ExtraHeaders), + foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2); + ?TYPE_GNU_LONGLINK -> + {RealName, Reader3} = get_real_name(Reader2), + ExtraHeaders2 = maps:put(?PAX_LINKPATH, + parse_string(RealName), ExtraHeaders), + foldl_read1(Fun, Accu0, Reader3, Opts, ExtraHeaders2); + _ -> + Header1 = merge_pax(Header, ExtraHeaders), + {ok, NewAccu, Reader3} = Fun(Header1, Reader2, Opts, Accu0), + foldl_read1(Fun, NewAccu, Reader3, Opts, #{}) + end + end. + +%% Applies all known PAX attributes to the current tar header +-spec merge_pax(tar_header(), #{binary() => binary()}) -> tar_header(). +merge_pax(Header, ExtraHeaders) when is_map(ExtraHeaders) -> + do_merge_pax(Header, maps:to_list(ExtraHeaders)). + +do_merge_pax(Header, []) -> + Header; +do_merge_pax(Header, [{?PAX_PATH, Path}|Rest]) -> + do_merge_pax(Header#tar_header{name=unicode:characters_to_list(Path)}, Rest); +do_merge_pax(Header, [{?PAX_LINKPATH, LinkPath}|Rest]) -> + do_merge_pax(Header#tar_header{linkname=unicode:characters_to_list(LinkPath)}, Rest); +do_merge_pax(Header, [{?PAX_GNAME, Gname}|Rest]) -> + do_merge_pax(Header#tar_header{gname=unicode:characters_to_list(Gname)}, Rest); +do_merge_pax(Header, [{?PAX_UNAME, Uname}|Rest]) -> + do_merge_pax(Header#tar_header{uname=unicode:characters_to_list(Uname)}, Rest); +do_merge_pax(Header, [{?PAX_UID, Uid}|Rest]) -> + Uid2 = binary_to_integer(Uid), + do_merge_pax(Header#tar_header{uid=Uid2}, Rest); +do_merge_pax(Header, [{?PAX_GID, Gid}|Rest]) -> + Gid2 = binary_to_integer(Gid), + do_merge_pax(Header#tar_header{gid=Gid2}, Rest); +do_merge_pax(Header, [{?PAX_ATIME, Atime}|Rest]) -> + Atime2 = parse_pax_time(Atime), + do_merge_pax(Header#tar_header{atime=Atime2}, Rest); +do_merge_pax(Header, [{?PAX_MTIME, Mtime}|Rest]) -> + Mtime2 = parse_pax_time(Mtime), + do_merge_pax(Header#tar_header{mtime=Mtime2}, Rest); +do_merge_pax(Header, [{?PAX_CTIME, Ctime}|Rest]) -> + Ctime2 = parse_pax_time(Ctime), + do_merge_pax(Header#tar_header{ctime=Ctime2}, Rest); +do_merge_pax(Header, [{?PAX_SIZE, Size}|Rest]) -> + Size2 = binary_to_integer(Size), + do_merge_pax(Header#tar_header{size=Size2}, Rest); +do_merge_pax(Header, [{<<?PAX_XATTR_STR, _Key/binary>>, _Value}|Rest]) -> + do_merge_pax(Header, Rest); +do_merge_pax(Header, [_Ignore|Rest]) -> + do_merge_pax(Header, Rest). + +%% Returns the time since UNIX epoch as a datetime +-spec parse_pax_time(binary()) -> tar_time(). +parse_pax_time(Bin) when is_binary(Bin) -> + TotalNano = case binary:split(Bin, [<<$.>>]) of + [SecondsStr, NanoStr0] -> + Seconds = binary_to_integer(SecondsStr), + if byte_size(NanoStr0) < ?MAX_NANO_INT_SIZE -> + %% right pad + PaddingN = ?MAX_NANO_INT_SIZE-byte_size(NanoStr0), + Padding = binary:copy(<<$0>>, PaddingN), + NanoStr1 = <<NanoStr0/binary,Padding/binary>>, + Nano = binary_to_integer(NanoStr1), + (Seconds*?BILLION)+Nano; + byte_size(NanoStr0) > ?MAX_NANO_INT_SIZE -> + %% right truncate + NanoStr1 = binary_part(NanoStr0, 0, ?MAX_NANO_INT_SIZE), + Nano = binary_to_integer(NanoStr1), + (Seconds*?BILLION)+Nano; + true -> + (Seconds*?BILLION)+binary_to_integer(NanoStr0) + end; + [SecondsStr] -> + binary_to_integer(SecondsStr)*?BILLION + end, + %% truncate to microseconds + Micro = TotalNano div 1000, + Mega = Micro div 1000000000000, + Secs = Micro div 1000000 - (Mega*1000000), + Secs. + +%% Given a regular file reader, reads the whole file and +%% parses all extended attributes it contains. +parse_pax(#reg_file_reader{handle=Handle,num_bytes=0}) -> + {#{}, Handle}; +parse_pax(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) -> + case do_read(Handle0, NumBytes) of + {ok, Bytes, Handle1} -> + do_parse_pax(Handle1, Bytes, #{}); + {error, _} = Err -> + throw(Err) + end. + +do_parse_pax(Reader, <<>>, Headers) -> + {Headers, Reader}; +do_parse_pax(Reader, Bin, Headers) -> + {Key, Value, Residual} = parse_pax_record(Bin), + NewHeaders = maps:put(Key, Value, Headers), + do_parse_pax(Reader, Residual, NewHeaders). + +%% Parse an extended attribute +parse_pax_record(Bin) when is_binary(Bin) -> + case binary:split(Bin, [<<$\n>>]) of + [Record, Residual] -> + case [X || X <- binary:split(Record, [<<$\s>>], [global]), X =/= <<>>] of + [_Len, Record1] -> + case [X || X <- binary:split(Record1, [<<$=>>], [global]), X =/= <<>>] of + [AttrName, AttrValue] -> + {AttrName, AttrValue, Residual}; + _Other -> + throw({error, malformed_pax_record}) + end; + _Other -> + throw({error, malformed_pax_record}) + end; + _Other -> + throw({error, malformed_pax_record}) + end. + +get_real_name(#reg_file_reader{handle=Handle,num_bytes=0}) -> + {"", Handle}; +get_real_name(#reg_file_reader{handle=Handle0,num_bytes=NumBytes}) -> + case do_read(Handle0, NumBytes) of + {ok, RealName, Handle1} -> + {RealName, Handle1}; + {error, _} = Err -> + throw(Err) + end; +get_real_name(#sparse_file_reader{num_bytes=NumBytes}=Reader0) -> + case do_read(Reader0, NumBytes) of + {ok, RealName, Reader1} -> + {RealName, Reader1}; + {error, _} = Err -> + throw(Err) + end. + +%% Skip the remaining bytes for the current file entry +skip_file(#reg_file_reader{handle=Handle0,pos=Pos,size=Size}=Reader) -> + Padding = skip_padding(Size), + AbsPos = Handle0#reader.pos + (Size-Pos) + Padding, + case do_position(Handle0, AbsPos) of + {ok, _, Handle1} -> + Reader#reg_file_reader{handle=Handle1,num_bytes=0,pos=Size}; + Err -> + throw(Err) + end; +skip_file(#sparse_file_reader{pos=Pos,size=Size}=Reader) -> + case do_read(Reader, Size-Pos) of + {ok, _, Reader2} -> + Reader2; + Err -> + throw(Err) + end. + +skip_padding(0) -> + 0; +skip_padding(Size) when (Size rem ?BLOCK_SIZE) =:= 0 -> + 0; +skip_padding(Size) when Size =< ?BLOCK_SIZE -> + ?BLOCK_SIZE - Size; +skip_padding(Size) -> + ?BLOCK_SIZE - (Size rem ?BLOCK_SIZE). + +skip_unread(#reader{pos=Pos}=Reader0) when (Pos rem ?BLOCK_SIZE) > 0 -> + Padding = skip_padding(Pos + ?BLOCK_SIZE), + AbsPos = Pos + Padding, + case do_position(Reader0, AbsPos) of + {ok, _, Reader1} -> + {ok, Reader1}; + Err -> + throw(Err) + end; +skip_unread(#reader{}=Reader) -> + {ok, Reader}; +skip_unread(#reg_file_reader{handle=Handle,num_bytes=0}) -> + skip_unread(Handle); +skip_unread(#reg_file_reader{}=Reader) -> + #reg_file_reader{handle=Handle} = skip_file(Reader), + {ok, Handle}; +skip_unread(#sparse_file_reader{handle=Handle,num_bytes=0}) -> + skip_unread(Handle); +skip_unread(#sparse_file_reader{}=Reader) -> + #sparse_file_reader{handle=Handle} = skip_file(Reader), + {ok, Handle}. + +write_extracted_element(#tar_header{name=Name,typeflag=Type}, + Bin, + #read_opts{output=memory}=Opts) -> + case typeflag(Type) of + regular -> + read_verbose(Opts, "x ~ts~n", [Name]), + {ok, {Name, Bin}}; + _ -> + ok + end; +write_extracted_element(#tar_header{name=Name0}=Header, Bin, Opts) -> + Name1 = make_safe_path(Name0, Opts), + Created = + case typeflag(Header#tar_header.typeflag) of + regular -> + create_regular(Name1, Name0, Bin, Opts); + directory -> + read_verbose(Opts, "x ~ts~n", [Name0]), + create_extracted_dir(Name1, Opts); + symlink -> + read_verbose(Opts, "x ~ts~n", [Name0]), + create_symlink(Name1, Header#tar_header.linkname, Opts); + Device when Device =:= char orelse Device =:= block -> + %% char/block devices will be created as empty files + %% and then have their major/minor device set later + create_regular(Name1, Name0, <<>>, Opts); + fifo -> + %% fifo devices will be created as empty files + create_regular(Name1, Name0, <<>>, Opts); + Other -> % Ignore. + read_verbose(Opts, "x ~ts - unsupported type ~p~n", + [Name0, Other]), + not_written + end, + case Created of + ok -> set_extracted_file_info(Name1, Header); + not_written -> ok + end. + +make_safe_path([$/|Path], Opts) -> + make_safe_path(Path, Opts); +make_safe_path(Path, #read_opts{cwd=Cwd}) -> + case r3_hex_filename:safe_relative_path(Path) of + unsafe -> + throw({error,{Path,unsafe_path}}); + SafePath -> + filename:absname(SafePath, Cwd) + end. + +create_regular(Name, NameInArchive, Bin, Opts) -> + case write_extracted_file(Name, Bin, Opts) of + not_written -> + read_verbose(Opts, "x ~ts - exists, not created~n", [NameInArchive]), + not_written; + Ok -> + read_verbose(Opts, "x ~ts~n", [NameInArchive]), + Ok + end. + +create_extracted_dir(Name, _Opts) -> + case file:make_dir(Name) of + ok -> ok; + {error,enotsup} -> not_written; + {error,eexist} -> not_written; + {error,enoent} -> make_dirs(Name, dir); + {error,Reason} -> throw({error, Reason}) + end. + +create_symlink(Name, Linkname, Opts) -> + case file:make_symlink(Linkname, Name) of + ok -> ok; + {error,enoent} -> + ok = make_dirs(Name, file), + create_symlink(Name, Linkname, Opts); + {error,eexist} -> not_written; + {error,enotsup} -> + read_verbose(Opts, "x ~ts - symbolic links not supported~n", [Name]), + not_written; + {error,Reason} -> throw({error, Reason}) + end. + +write_extracted_file(Name, Bin, Opts) -> + Write = + case Opts#read_opts.keep_old_files of + true -> + case file:read_file_info(Name) of + {ok, _} -> false; + _ -> true + end; + false -> true + end, + case Write of + true -> write_file(Name, Bin); + false -> not_written + end. + +write_file(Name, Bin) -> + case file:write_file(Name, Bin) of + ok -> ok; + {error,enoent} -> + case make_dirs(Name, file) of + ok -> + write_file(Name, Bin); + {error,Reason} -> + throw({error, Reason}) + end; + {error,Reason} -> + throw({error, Reason}) + end. + +set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_SYMLINK}) -> ok; +set_extracted_file_info(_, #tar_header{typeflag = ?TYPE_LINK}) -> ok; +set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_CHAR}=Header) -> + set_device_info(Name, Header); +set_extracted_file_info(Name, #tar_header{typeflag = ?TYPE_BLOCK}=Header) -> + set_device_info(Name, Header); +set_extracted_file_info(Name, #tar_header{mtime=Mtime,mode=Mode}) -> + Info = #file_info{mode=Mode, mtime=Mtime}, + file:write_file_info(Name, Info, [{time, posix}]). + +set_device_info(Name, #tar_header{}=Header) -> + Mtime = Header#tar_header.mtime, + Mode = Header#tar_header.mode, + Devmajor = Header#tar_header.devmajor, + Devminor = Header#tar_header.devminor, + Info = #file_info{ + mode=Mode, + mtime=Mtime, + major_device=Devmajor, + minor_device=Devminor + }, + file:write_file_info(Name, Info). + +%% Makes all directories leading up to the file. + +make_dirs(Name, file) -> + filelib:ensure_dir(Name); +make_dirs(Name, dir) -> + filelib:ensure_dir(filename:join(Name,"*")). + +%% Prints the message on if the verbose option is given (for reading). +read_verbose(#read_opts{verbose=true}, Format, Args) -> + io:format(Format, Args); +read_verbose(_, _, _) -> + ok. + +%% Prints the message on if the verbose option is given. +add_verbose(#add_opts{verbose=true}, Format, Args) -> + io:format(Format, Args); +add_verbose(_, _, _) -> + ok. + +%%%%%%%%%%%%%%%%%% +%% I/O primitives +%%%%%%%%%%%%%%%%%% + +do_write(#reader{handle=Handle,func=Fun}=Reader0, Data) + when is_function(Fun,2) -> + case Fun(write,{Handle,Data}) of + ok -> + {ok, Pos, Reader1} = do_position(Reader0, {cur,0}), + {ok, Reader1#reader{pos=Pos}}; + {error, _} = Err -> + Err + end. + +do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=0}=Opts) + when is_function(Fun, 2) -> + do_copy(Reader, Source, Opts#add_opts{chunk_size=65536}); +do_copy(#reader{func=Fun}=Reader, Source, #add_opts{chunk_size=ChunkSize}) + when is_function(Fun, 2) -> + case file:open(Source, [read, binary]) of + {ok, SourceFd} -> + case copy_chunked(Reader, SourceFd, ChunkSize, 0) of + {ok, _Copied, _Reader2} = Ok-> + _ = file:close(SourceFd), + Ok; + Err -> + _ = file:close(SourceFd), + throw(Err) + end; + Err -> + throw(Err) + end. + +copy_chunked(#reader{}=Reader, Source, ChunkSize, Copied) -> + case file:read(Source, ChunkSize) of + {ok, Bin} -> + {ok, Reader2} = do_write(Reader, Bin), + copy_chunked(Reader2, Source, ChunkSize, Copied+byte_size(Bin)); + eof -> + {ok, Copied, Reader}; + Other -> + Other + end. + + +do_position(#reader{handle=Handle,func=Fun}=Reader, Pos) + when is_function(Fun,2)-> + case Fun(position, {Handle,Pos}) of + {ok, NewPos} -> + %% since Pos may not always be an absolute seek, + %% make sure we update the reader with the new absolute position + {ok, AbsPos} = Fun(position, {Handle, {cur, 0}}), + {ok, NewPos, Reader#reader{pos=AbsPos}}; + Other -> + Other + end. + +do_read(#reg_file_reader{handle=Handle,pos=Pos,size=Size}=Reader, Len) -> + NumBytes = Size - Pos, + ActualLen = if NumBytes - Len < 0 -> NumBytes; true -> Len end, + case do_read(Handle, ActualLen) of + {ok, Bin, Handle2} -> + NewPos = Pos + ActualLen, + NumBytes2 = Size - NewPos, + Reader1 = Reader#reg_file_reader{ + handle=Handle2, + pos=NewPos, + num_bytes=NumBytes2}, + {ok, Bin, Reader1}; + Other -> + Other + end; +do_read(#sparse_file_reader{}=Reader, Len) -> + do_sparse_read(Reader, Len); +do_read(#reader{pos=Pos,handle=Handle,func=Fun}=Reader, Len) + when is_function(Fun,2)-> + %% Always convert to binary internally + case Fun(read2,{Handle,Len}) of + {ok, List} when is_list(List) -> + Bin = list_to_binary(List), + NewPos = Pos+byte_size(Bin), + {ok, Bin, Reader#reader{pos=NewPos}}; + {ok, Bin} when is_binary(Bin) -> + NewPos = Pos+byte_size(Bin), + {ok, Bin, Reader#reader{pos=NewPos}}; + Other -> + Other + end. + + +do_sparse_read(Reader, Len) -> + do_sparse_read(Reader, Len, <<>>). + +do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{num_bytes=0}|Entries] + }=Reader0, Len, Acc) -> + %% skip all empty fragments + Reader1 = Reader0#sparse_file_reader{sparse_map=Entries}, + do_sparse_read(Reader1, Len, Acc); +do_sparse_read(#sparse_file_reader{sparse_map=[], + pos=Pos,size=Size}=Reader0, Len, Acc) + when Pos < Size -> + %% if there are no more fragments, it is possible that there is one last sparse hole + %% this behaviour matches the BSD tar utility + %% however, GNU tar stops returning data even if we haven't reached the end + {ok, Bin, Reader1} = read_sparse_hole(Reader0, Size, Len), + do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); +do_sparse_read(#sparse_file_reader{sparse_map=[]}=Reader, _Len, Acc) -> + {ok, Acc, Reader}; +do_sparse_read(#sparse_file_reader{}=Reader, 0, Acc) -> + {ok, Acc, Reader}; +do_sparse_read(#sparse_file_reader{sparse_map=[#sparse_entry{offset=Offset}|_], + pos=Pos}=Reader0, Len, Acc) + when Pos < Offset -> + {ok, Bin, Reader1} = read_sparse_hole(Reader0, Offset, Offset-Pos), + do_sparse_read(Reader1, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); +do_sparse_read(#sparse_file_reader{sparse_map=[Entry|Entries], + pos=Pos}=Reader0, Len, Acc) -> + %% we're in a data fragment, so read from it + %% end offset of fragment + EndPos = Entry#sparse_entry.offset + Entry#sparse_entry.num_bytes, + %% bytes left in fragment + NumBytes = EndPos - Pos, + ActualLen = if Len > NumBytes -> NumBytes; true -> Len end, + case do_read(Reader0#sparse_file_reader.handle, ActualLen) of + {ok, Bin, Handle} -> + BytesRead = byte_size(Bin), + ActualEndPos = Pos+BytesRead, + Reader1 = if ActualEndPos =:= EndPos -> + Reader0#sparse_file_reader{sparse_map=Entries}; + true -> + Reader0 + end, + Size = Reader1#sparse_file_reader.size, + NumBytes2 = Size - ActualEndPos, + Reader2 = Reader1#sparse_file_reader{ + handle=Handle, + pos=ActualEndPos, + num_bytes=NumBytes2}, + do_sparse_read(Reader2, Len-byte_size(Bin), <<Acc/binary,Bin/binary>>); + Other -> + Other + end. + +%% Reads a sparse hole ending at Offset +read_sparse_hole(#sparse_file_reader{pos=Pos}=Reader, Offset, Len) -> + N = Offset - Pos, + N2 = if N > Len -> + Len; + true -> + N + end, + Bin = <<0:N2/unit:8>>, + NumBytes = Reader#sparse_file_reader.size - (Pos+N2), + {ok, Bin, Reader#sparse_file_reader{ + num_bytes=NumBytes, + pos=Pos+N2}}. + +-spec do_close(reader()) -> ok | {error, term()}. +do_close(#reader{handle=Handle,func=Fun}) when is_function(Fun,2) -> + Fun(close,Handle). + +%%%%%%%%%%%%%%%%%% +%% Option parsing +%%%%%%%%%%%%%%%%%% + +extract_opts(List) -> + extract_opts(List, default_options()). + +table_opts(List) -> + read_opts(List, default_options()). + +default_options() -> + {ok, Cwd} = file:get_cwd(), + #read_opts{cwd=Cwd}. + +extract_opts([keep_old_files|Rest], Opts) -> + extract_opts(Rest, Opts#read_opts{keep_old_files=true}); +extract_opts([{cwd, Cwd}|Rest], Opts) -> + extract_opts(Rest, Opts#read_opts{cwd=Cwd}); +extract_opts([{files, Files}|Rest], Opts) -> + Set = ordsets:from_list(Files), + extract_opts(Rest, Opts#read_opts{files=Set}); +extract_opts([memory|Rest], Opts) -> + extract_opts(Rest, Opts#read_opts{output=memory}); +extract_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> + extract_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]}); +extract_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> + extract_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]}); +extract_opts([verbose|Rest], Opts) -> + extract_opts(Rest, Opts#read_opts{verbose=true}); +extract_opts([Other|Rest], Opts) -> + extract_opts(Rest, read_opts([Other], Opts)); +extract_opts([], Opts) -> + Opts. + +read_opts([compressed|Rest], Opts=#read_opts{open_mode=OpenMode}) -> + read_opts(Rest, Opts#read_opts{open_mode=[compressed|OpenMode]}); +read_opts([cooked|Rest], Opts=#read_opts{open_mode=OpenMode}) -> + read_opts(Rest, Opts#read_opts{open_mode=[cooked|OpenMode]}); +read_opts([verbose|Rest], Opts) -> + read_opts(Rest, Opts#read_opts{verbose=true}); +read_opts([_|Rest], Opts) -> + read_opts(Rest, Opts); +read_opts([], Opts) -> + Opts. diff --git a/src/r3_hex_erl_tar.hrl b/src/r3_hex_erl_tar.hrl new file mode 100644 index 0000000..4834b2d --- /dev/null +++ b/src/r3_hex_erl_tar.hrl @@ -0,0 +1,405 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +% Copied from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/erl_tar.hrl + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%% Options used when adding files to a tar archive. +-record(add_opts, { + read_info, %% Fun to use for read file/link info. + chunk_size = 0, %% For file reading when sending to sftp. 0=do not chunk + verbose = false, %% Verbose on/off. + atime = undefined, + mtime = undefined, + ctime = undefined, + uid = 0, + gid = 0}). +-type add_opts() :: #add_opts{}. + +%% Options used when reading a tar archive. +-record(read_opts, { + cwd :: string(), %% Current working directory. + keep_old_files = false :: boolean(), %% Owerwrite or not. + files = all, %% Set of files to extract (or all) + output = file :: 'file' | 'memory', + open_mode = [], %% Open mode options. + verbose = false :: boolean()}). %% Verbose on/off. +-type read_opts() :: #read_opts{}. + +-type add_opt() :: dereference | + verbose | + {chunks, pos_integer()}. + +-type extract_opt() :: {cwd, string()} | + {files, [string()]} | + compressed | + cooked | + memory | + keep_old_files | + verbose. + +-type create_opt() :: compressed | + cooked | + dereference | + verbose. + +-type filelist() :: [file:filename() | + {string(), binary()} | + {string(), file:filename()}]. + +-type tar_time() :: non_neg_integer(). + +%% The tar header, once fully parsed. +-record(tar_header, { + name = "" :: string(), %% name of header file entry + mode = 8#100644 :: non_neg_integer(), %% permission and mode bits + uid = 0 :: non_neg_integer(), %% user id of owner + gid = 0 :: non_neg_integer(), %% group id of owner + size = 0 :: non_neg_integer(), %% length in bytes + mtime :: tar_time(), %% modified time + typeflag :: char(), %% type of header entry + linkname = "" :: string(), %% target name of link + uname = "" :: string(), %% user name of owner + gname = "" :: string(), %% group name of owner + devmajor = 0 :: non_neg_integer(), %% major number of character or block device + devminor = 0 :: non_neg_integer(), %% minor number of character or block device + atime :: tar_time(), %% access time + ctime :: tar_time() %% status change time + }). +-type tar_header() :: #tar_header{}. + +%% Metadata for a sparse file fragment +-record(sparse_entry, { + offset = 0 :: non_neg_integer(), + num_bytes = 0 :: non_neg_integer()}). +-type sparse_entry() :: #sparse_entry{}. +%% Contains metadata about fragments of a sparse file +-record(sparse_array, { + entries = [] :: [sparse_entry()], + is_extended = false :: boolean(), + max_entries = 0 :: non_neg_integer()}). +-type sparse_array() :: #sparse_array{}. +%% A subset of tar header fields common to all tar implementations +-record(header_v7, { + name :: binary(), + mode :: binary(), %% octal + uid :: binary(), %% integer + gid :: binary(), %% integer + size :: binary(), %% integer + mtime :: binary(), %% integer + checksum :: binary(), %% integer + typeflag :: byte(), %% char + linkname :: binary()}). +-type header_v7() :: #header_v7{}. +%% The set of fields specific to GNU tar formatted archives +-record(header_gnu, { + header_v7 :: header_v7(), + magic :: binary(), + version :: binary(), + uname :: binary(), + gname :: binary(), + devmajor :: binary(), %% integer + devminor :: binary(), %% integer + atime :: binary(), %% integer + ctime :: binary(), %% integer + sparse :: sparse_array(), + real_size :: binary()}). %% integer +-type header_gnu() :: #header_gnu{}. +%% The set of fields specific to STAR-formatted archives +-record(header_star, { + header_v7 :: header_v7(), + magic :: binary(), + version :: binary(), + uname :: binary(), + gname :: binary(), + devmajor :: binary(), %% integer + devminor :: binary(), %% integer + prefix :: binary(), + atime :: binary(), %% integer + ctime :: binary(), %% integer + trailer :: binary()}). +-type header_star() :: #header_star{}. +%% The set of fields specific to USTAR-formatted archives +-record(header_ustar, { + header_v7 :: header_v7(), + magic :: binary(), + version :: binary(), + uname :: binary(), + gname :: binary(), + devmajor :: binary(), %% integer + devminor :: binary(), %% integer + prefix :: binary()}). +-type header_ustar() :: #header_ustar{}. + +-type header_fields() :: header_v7() | + header_gnu() | + header_star() | + header_ustar(). + +%% The overall tar reader, it holds the low-level file handle, +%% its access, position, and the I/O primitives wrapper. +-record(reader, { + handle :: file:io_device() | term(), + access :: read | write | ram, + pos = 0 :: non_neg_integer(), + func :: file_op() + }). +-type reader() :: #reader{}. +%% A reader for a regular file within the tar archive, +%% It tracks its current state relative to that file. +-record(reg_file_reader, { + handle :: reader(), + num_bytes = 0, + pos = 0, + size = 0 + }). +-type reg_file_reader() :: #reg_file_reader{}. +%% A reader for a sparse file within the tar archive, +%% It tracks its current state relative to that file. +-record(sparse_file_reader, { + handle :: reader(), + num_bytes = 0, %% bytes remaining + pos = 0, %% pos + size = 0, %% total size of file + sparse_map = #sparse_array{} + }). +-type sparse_file_reader() :: #sparse_file_reader{}. + +%% Types for the readers +-type reader_type() :: reader() | reg_file_reader() | sparse_file_reader(). +-type handle() :: file:io_device() | term(). + +%% Type for the I/O primitive wrapper function +-type file_op() :: fun((write | close | read2 | position, + {handle(), iodata()} | handle() | {handle(), non_neg_integer()} + | {handle(), non_neg_integer()}) -> + ok | eof | {ok, string() | binary()} | {ok, non_neg_integer()} + | {error, term()}). + +%% These constants (except S_IFMT) are +%% used to determine what type of device +%% a file is. Namely, `S_IFMT band file_info.mode` +%% will equal one of these contants, and tells us +%% which type it is. The stdlib file_info record +%% does not differentiate between device types, and +%% will not allow us to differentiate between sockets +%% and named pipes. These constants are pulled from libc. +-define(S_IFMT, 61440). +-define(S_IFSOCK, 49152). %% socket +-define(S_FIFO, 4096). %% fifo/named pipe +-define(S_IFBLK, 24576). %% block device +-define(S_IFCHR, 8192). %% character device + +%% Typeflag constants for the tar header +-define(TYPE_REGULAR, $0). %% regular file +-define(TYPE_REGULAR_A, 0). %% regular file +-define(TYPE_LINK, $1). %% hard link +-define(TYPE_SYMLINK, $2). %% symbolic link +-define(TYPE_CHAR, $3). %% character device node +-define(TYPE_BLOCK, $4). %% block device node +-define(TYPE_DIR, $5). %% directory +-define(TYPE_FIFO, $6). %% fifo node +-define(TYPE_CONT, $7). %% reserved +-define(TYPE_X_HEADER, $x). %% extended header +-define(TYPE_X_GLOBAL_HEADER, $g). %% global extended header +-define(TYPE_GNU_LONGNAME, $L). %% next file has a long name +-define(TYPE_GNU_LONGLINK, $K). %% next file symlinks to a file with a long name +-define(TYPE_GNU_SPARSE, $S). %% sparse file + +%% Mode constants from tar spec +-define(MODE_ISUID, 4000). %% set uid +-define(MODE_ISGID, 2000). %% set gid +-define(MODE_ISVTX, 1000). %% save text (sticky bit) +-define(MODE_ISDIR, 40000). %% directory +-define(MODE_ISFIFO, 10000). %% fifo +-define(MODE_ISREG, 100000). %% regular file +-define(MODE_ISLNK, 120000). %% symbolic link +-define(MODE_ISBLK, 60000). %% block special file +-define(MODE_ISCHR, 20000). %% character special file +-define(MODE_ISSOCK, 140000). %% socket + +%% Keywords for PAX extended header +-define(PAX_ATIME, <<"atime">>). +-define(PAX_CHARSET, <<"charset">>). +-define(PAX_COMMENT, <<"comment">>). +-define(PAX_CTIME, <<"ctime">>). %% ctime is not a valid pax header +-define(PAX_GID, <<"gid">>). +-define(PAX_GNAME, <<"gname">>). +-define(PAX_LINKPATH, <<"linkpath">>). +-define(PAX_MTIME, <<"mtime">>). +-define(PAX_PATH, <<"path">>). +-define(PAX_SIZE, <<"size">>). +-define(PAX_UID, <<"uid">>). +-define(PAX_UNAME, <<"uname">>). +-define(PAX_XATTR, <<"SCHILY.xattr.">>). +-define(PAX_XATTR_STR, "SCHILY.xattr."). +-define(PAX_NONE, <<"">>). + +%% Tar format constants +%% Unknown format +-define(FORMAT_UNKNOWN, 0). +%% The format of the original Unix V7 tar tool prior to standardization +-define(FORMAT_V7, 1). +%% The old and new GNU formats, incompatible with USTAR. +%% This covers the old GNU sparse extension, but it does +%% not cover the GNU sparse extensions using PAX headers, +%% versions 0.0, 0.1, and 1.0; these fall under the PAX format. +-define(FORMAT_GNU, 2). +%% Schily's tar format, which is incompatible with USTAR. +%% This does not cover STAR extensions to the PAX format; these +%% fall under the PAX format. +-define(FORMAT_STAR, 3). +%% USTAR is the former standardization of tar defined in POSIX.1-1988, +%% it is incompatible with the GNU and STAR formats. +-define(FORMAT_USTAR, 4). +%% PAX is the latest standardization of tar defined in POSIX.1-2001. +%% This is an extension of USTAR and is "backwards compatible" with it. +%% +%% Some newer formats add their own extensions to PAX, such as GNU sparse +%% files and SCHILY extended attributes. Since they are backwards compatible +%% with PAX, they will be labelled as "PAX". +-define(FORMAT_PAX, 5). + +%% Magic constants +-define(MAGIC_GNU, <<"ustar ">>). +-define(VERSION_GNU, <<" \x00">>). +-define(MAGIC_USTAR, <<"ustar\x00">>). +-define(VERSION_USTAR, <<"00">>). +-define(TRAILER_STAR, <<"tar\x00">>). + +%% Size constants +-define(BLOCK_SIZE, 512). %% size of each block in a tar stream +-define(NAME_SIZE, 100). %% max length of the name field in USTAR format +-define(PREFIX_SIZE, 155). %% max length of the prefix field in USTAR format + +%% Maximum size of a nanosecond value as an integer +-define(MAX_NANO_INT_SIZE, 9). +%% Maximum size of a 64-bit signed integer +-define(MAX_INT64, (1 bsl 63 - 1)). + +-define(PAX_GNU_SPARSE_NUMBLOCKS, <<"GNU.sparse.numblocks">>). +-define(PAX_GNU_SPARSE_OFFSET, <<"GNU.sparse.offset">>). +-define(PAX_GNU_SPARSE_NUMBYTES, <<"GNU.sparse.numbytes">>). +-define(PAX_GNU_SPARSE_MAP, <<"GNU.sparse.map">>). +-define(PAX_GNU_SPARSE_NAME, <<"GNU.sparse.name">>). +-define(PAX_GNU_SPARSE_MAJOR, <<"GNU.sparse.major">>). +-define(PAX_GNU_SPARSE_MINOR, <<"GNU.sparse.minor">>). +-define(PAX_GNU_SPARSE_SIZE, <<"GNU.sparse.size">>). +-define(PAX_GNU_SPARSE_REALSIZE, <<"GNU.sparse.realsize">>). + +-define(V7_NAME, 0). +-define(V7_NAME_LEN, 100). +-define(V7_MODE, 100). +-define(V7_MODE_LEN, 8). +-define(V7_UID, 108). +-define(V7_UID_LEN, 8). +-define(V7_GID, 116). +-define(V7_GID_LEN, 8). +-define(V7_SIZE, 124). +-define(V7_SIZE_LEN, 12). +-define(V7_MTIME, 136). +-define(V7_MTIME_LEN, 12). +-define(V7_CHKSUM, 148). +-define(V7_CHKSUM_LEN, 8). +-define(V7_TYPE, 156). +-define(V7_TYPE_LEN, 1). +-define(V7_LINKNAME, 157). +-define(V7_LINKNAME_LEN, 100). + +-define(STAR_TRAILER, 508). +-define(STAR_TRAILER_LEN, 4). + +-define(USTAR_MAGIC, 257). +-define(USTAR_MAGIC_LEN, 6). +-define(USTAR_VERSION, 263). +-define(USTAR_VERSION_LEN, 2). +-define(USTAR_UNAME, 265). +-define(USTAR_UNAME_LEN, 32). +-define(USTAR_GNAME, 297). +-define(USTAR_GNAME_LEN, 32). +-define(USTAR_DEVMAJ, 329). +-define(USTAR_DEVMAJ_LEN, 8). +-define(USTAR_DEVMIN, 337). +-define(USTAR_DEVMIN_LEN, 8). +-define(USTAR_PREFIX, 345). +-define(USTAR_PREFIX_LEN, 155). + +-define(GNU_MAGIC, 257). +-define(GNU_MAGIC_LEN, 6). +-define(GNU_VERSION, 263). +-define(GNU_VERSION_LEN, 2). + +%% ?BLOCK_SIZE of zero-bytes. +%% Two of these in a row mark the end of an archive. +-define(ZERO_BLOCK, <<0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0>>). + +-define(BILLION, 1000000000). + +-define(EPOCH, {{1970,1,1}, {0,0,0}}). diff --git a/src/r3_hex_filename.erl b/src/r3_hex_filename.erl new file mode 100644 index 0000000..f04c72f --- /dev/null +++ b/src/r3_hex_filename.erl @@ -0,0 +1,60 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +% @private +% Excerpt from https://github.com/erlang/otp/blob/OTP-20.0.1/lib/stdlib/src/filename.erl#L761-L788 +% with modifications for changing local function calls to remote function calls +% to the `filename` module, for the functions `pathtype/1`, `split/1`, and `join/1` +% +% safe_relative_path/1 was not present in earlier OTP releases. + +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2017. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(r3_hex_filename). +-export([safe_relative_path/1]). + +safe_relative_path(Path) -> + case filename:pathtype(Path) of + relative -> + Cs0 = filename:split(Path), + safe_relative_path_1(Cs0, []); + _ -> + unsafe + end. + +safe_relative_path_1(["."|T], Acc) -> + safe_relative_path_1(T, Acc); +safe_relative_path_1([<<".">>|T], Acc) -> + safe_relative_path_1(T, Acc); +safe_relative_path_1([".."|T], Acc) -> + climb(T, Acc); +safe_relative_path_1([<<"..">>|T], Acc) -> + climb(T, Acc); +safe_relative_path_1([H|T], Acc) -> + safe_relative_path_1(T, [H|Acc]); +safe_relative_path_1([], []) -> + []; +safe_relative_path_1([], Acc) -> + filename:join(lists:reverse(Acc)). + +climb(_, []) -> + unsafe; +climb(T, [_|Acc]) -> + safe_relative_path_1(T, Acc). diff --git a/src/r3_hex_http.erl b/src/r3_hex_http.erl new file mode 100644 index 0000000..b4794b5 --- /dev/null +++ b/src/r3_hex_http.erl @@ -0,0 +1,43 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_http). +-export([request/5]). +-ifdef(TEST). +-export([user_agent/1]). +-endif. +-include_lib("r3_hex_core.hrl"). + +-type method() :: get | post | put | patch | delete. +-type status() :: non_neg_integer(). +-type headers() :: #{binary() => binary()}. +-type body() :: {ContentType :: binary(), Body :: binary()} | undefined. +-type adapter_config() :: map(). + +-callback request(method(), URI :: binary(), headers(), body(), adapter_config()) -> + {ok, status(), headers(), binary()} | + {error, term()}. + +-spec request(r3_hex_core:config(), method(), URI :: binary(), headers(), body()) -> + {ok, {status(), headers(), binary()}} | {error, term()}. +request(Config, Method, URI, Headers, Body) when is_binary(URI) and is_map(Headers) -> + Adapter = maps:get(http_adapter, Config), + UserAgentFragment = maps:get(http_user_agent_fragment, Config), + Headers2 = put_new(<<"user-agent">>, user_agent(UserAgentFragment), Headers), + AdapterConfig = maps:get(http_adapter_config, Config, #{}), + Adapter:request(Method, URI, Headers2, Body, AdapterConfig). + +user_agent(UserAgentFragment) -> + OTPRelease = erlang:system_info(otp_release), + ERTSVersion = erlang:system_info(version), + OTPString = " (OTP/" ++ OTPRelease ++ ") (erts/" ++ ERTSVersion ++ ")", + iolist_to_binary(["hex_core/", ?HEX_CORE_VERSION, " ", UserAgentFragment, OTPString]). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +put_new(Key, Value, Map) -> + case maps:find(Key, Map) of + {ok, _} -> Map; + error -> maps:put(Key, Value, Map) + end. diff --git a/src/r3_hex_http_httpc.erl b/src/r3_hex_http_httpc.erl new file mode 100644 index 0000000..0be9d16 --- /dev/null +++ b/src/r3_hex_http_httpc.erl @@ -0,0 +1,39 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% @hidden + +-module(r3_hex_http_httpc). +-behaviour(r3_hex_http). +-export([request/5]). + +%%==================================================================== +%% API functions +%%==================================================================== + +request(Method, URI, ReqHeaders, Body, AdapterConfig) -> + Profile = maps:get(profile, AdapterConfig, default), + Request = build_request(URI, ReqHeaders, Body), + {ok, {{_, StatusCode, _}, RespHeaders, RespBody}} = + httpc:request(Method, Request, [], [{body_format, binary}], Profile), + RespHeaders2 = load_headers(RespHeaders), + {ok, {StatusCode, RespHeaders2, RespBody}}. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +build_request(URI, ReqHeaders, Body) -> + build_request2(binary_to_list(URI), dump_headers(ReqHeaders), Body). + +build_request2(URI, ReqHeaders, undefined) -> + {URI, ReqHeaders}; +build_request2(URI, ReqHeaders, {ContentType, Body}) -> + {URI, ReqHeaders, ContentType, Body}. + +dump_headers(Map) -> + maps:fold(fun(K, V, Acc) -> + [{binary_to_list(K), binary_to_list(V)} | Acc] end, [], Map). + +load_headers(List) -> + lists:foldl(fun({K, V}, Acc) -> + maps:put(list_to_binary(K), list_to_binary(V), Acc) end, #{}, List). diff --git a/src/r3_hex_pb_names.erl b/src/r3_hex_pb_names.erl new file mode 100644 index 0000000..455092c --- /dev/null +++ b/src/r3_hex_pb_names.erl @@ -0,0 +1,735 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_names). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Names'() :: + #{packages => ['Package'()], % = 1 + repository => iodata() % = 2 + }. + +-type 'Package'() :: + #{name => iodata() % = 1 + }. + +-export_type(['Names'/0, 'Package'/0]). + +-spec encode_msg('Names'() | 'Package'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> + encode_msg(Msg, MsgName, []). + +-spec encode_msg('Names'() | 'Package'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> + verify_msg(Msg, MsgName, Opts), + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Names' -> e_msg_Names(id(Msg, TrUserData), TrUserData); + 'Package' -> + e_msg_Package(id(Msg, TrUserData), TrUserData) + end. + + +e_msg_Names(Msg, TrUserData) -> + e_msg_Names(Msg, <<>>, TrUserData). + + +e_msg_Names(#{repository := F2} = M, Bin, TrUserData) -> + B1 = case M of + #{packages := F1} -> + TrF1 = id(F1, TrUserData), + if TrF1 == [] -> Bin; + true -> e_field_Names_packages(TrF1, Bin, TrUserData) + end; + _ -> Bin + end, + begin + TrF2 = id(F2, TrUserData), + e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) + end. + +e_msg_Package(Msg, TrUserData) -> + e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F1}, Bin, TrUserData) -> + begin + TrF1 = id(F1, TrUserData), + e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) + end. + +e_mfield_Names_packages(Msg, Bin, TrUserData) -> + SubBin = e_msg_Package(Msg, <<>>, TrUserData), + Bin2 = e_varint(byte_size(SubBin), Bin), + <<Bin2/binary, SubBin/binary>>. + +e_field_Names_packages([Elem | Rest], Bin, + TrUserData) -> + Bin2 = <<Bin/binary, 10>>, + Bin3 = e_mfield_Names_packages(id(Elem, TrUserData), + Bin2, TrUserData), + e_field_Names_packages(Rest, Bin3, TrUserData); +e_field_Names_packages([], Bin, _TrUserData) -> Bin. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> + e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> + e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> + <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> + <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> + Utf8 = unicode:characters_to_binary(S), + Bin2 = e_varint(byte_size(Utf8), Bin), + <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) + when is_binary(Bytes) -> + Bin2 = e_varint(byte_size(Bytes), Bin), + <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) + when is_list(Bytes) -> + BytesBin = iolist_to_binary(Bytes), + Bin2 = e_varint(byte_size(BytesBin), Bin), + <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> + <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> + <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> + <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> + <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> + <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> + <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> + Bin2 = <<Bin/binary, (N band 127 bor 128)>>, + e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> + decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> + TrUserData = proplists:get_value(user_data, Opts), + decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason -> + StackTrace = erlang:get_stacktrace(), + error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-endif. + +-endif. + +decode_msg_2_doit('Names', Bin, TrUserData) -> + id(d_msg_Names(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Package', Bin, TrUserData) -> + id(d_msg_Package(Bin, TrUserData), TrUserData). + + + +d_msg_Names(Bin, TrUserData) -> + dfp_read_field_def_Names(Bin, 0, 0, id([], TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Names(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Names_packages(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Names(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Names_repository(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Names(<<>>, 0, 0, R1, F@_2, + TrUserData) -> + S1 = #{repository => F@_2}, + if R1 == '$undef' -> S1; + true -> S1#{packages => lists_reverse(R1, TrUserData)} + end; +dfp_read_field_def_Names(Other, Z1, Z2, F@_1, F@_2, + TrUserData) -> + dg_read_field_def_Names(Other, Z1, Z2, F@_1, F@_2, + TrUserData). + +dg_read_field_def_Names(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Names(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +dg_read_field_def_Names(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Names_packages(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 18 -> + d_field_Names_repository(Rest, 0, 0, F@_1, F@_2, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Names(Rest, 0, 0, F@_1, F@_2, TrUserData); + 1 -> skip_64_Names(Rest, 0, 0, F@_1, F@_2, TrUserData); + 2 -> + skip_length_delimited_Names(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 3 -> + skip_group_Names(Rest, Key bsr 3, 0, F@_1, F@_2, + TrUserData); + 5 -> skip_32_Names(Rest, 0, 0, F@_1, F@_2, TrUserData) + end + end; +dg_read_field_def_Names(<<>>, 0, 0, R1, F@_2, + TrUserData) -> + S1 = #{repository => F@_2}, + if R1 == '$undef' -> S1; + true -> S1#{packages => lists_reverse(R1, TrUserData)} + end. + +d_field_Names_packages(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Names_packages(Rest, N + 7, X bsl N + Acc, F@_1, + F@_2, TrUserData); +d_field_Names_packages(<<0:1, X:7, Rest/binary>>, N, + Acc, Prev, F@_2, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bs:Len/binary, Rest2/binary>> = Rest, + {id(d_msg_Package(Bs, TrUserData), TrUserData), + Rest2} + end, + dfp_read_field_def_Names(RestF, 0, 0, + cons(NewFValue, Prev, TrUserData), F@_2, + TrUserData). + +d_field_Names_repository(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Names_repository(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +d_field_Names_repository(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Names(RestF, 0, 0, F@_1, NewFValue, + TrUserData). + +skip_varint_Names(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + skip_varint_Names(Rest, Z1, Z2, F@_1, F@_2, TrUserData); +skip_varint_Names(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_length_delimited_Names(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + skip_length_delimited_Names(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +skip_length_delimited_Names(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Names(Rest2, 0, 0, F@_1, F@_2, + TrUserData). + +skip_group_Names(Bin, FNum, Z2, F@_1, F@_2, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Names(Rest, 0, Z2, F@_1, F@_2, + TrUserData). + +skip_32_Names(<<_:32, Rest/binary>>, Z1, Z2, F@_1, F@_2, + TrUserData) -> + dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_64_Names(<<_:64, Rest/binary>>, Z1, Z2, F@_1, F@_2, + TrUserData) -> + dfp_read_field_def_Names(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +d_msg_Package(Bin, TrUserData) -> + dfp_read_field_def_Package(Bin, 0, 0, + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, + F@_1, TrUserData) -> + d_field_Package_name(Rest, Z1, Z2, F@_1, TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, F@_1, _) -> + #{name => F@_1}; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, + TrUserData) -> + dg_read_field_def_Package(Other, Z1, Z2, F@_1, + TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, + F@_1, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Package_name(Rest, 0, 0, F@_1, TrUserData); + _ -> + case Key band 7 of + 0 -> skip_varint_Package(Rest, 0, 0, F@_1, TrUserData); + 1 -> skip_64_Package(Rest, 0, 0, F@_1, TrUserData); + 2 -> + skip_length_delimited_Package(Rest, 0, 0, F@_1, + TrUserData); + 3 -> + skip_group_Package(Rest, Key bsr 3, 0, F@_1, + TrUserData); + 5 -> skip_32_Package(Rest, 0, 0, F@_1, TrUserData) + end + end; +dg_read_field_def_Package(<<>>, 0, 0, F@_1, _) -> + #{name => F@_1}. + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, + F@_1, TrUserData) + when N < 57 -> + d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, + TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, + _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, NewFValue, + TrUserData). + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, TrUserData) -> + skip_varint_Package(Rest, Z1, Z2, F@_1, TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, + TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, TrUserData) + when N < 57 -> + skip_length_delimited_Package(Rest, N + 7, + X bsl N + Acc, F@_1, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Package(Rest2, 0, 0, F@_1, + TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Package(Rest, 0, Z2, F@_1, + TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, + TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, + TrUserData). + +read_group(Bin, FieldNum) -> + {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), + <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, + {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) + when N < (32-7) -> + read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, + FieldNum) -> + Key = X bsl N + Acc, + TagLen1 = TagLen + 1, + case {Key bsr 3, Key band 7} of + {FieldNum, 4} -> % 4 = group_end + {NumBytes, TagLen1}; + {_, 0} -> % 0 = varint + read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); + {_, 1} -> % 1 = bits64 + <<_:64, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); + {_, 2} -> % 2 = length_delimited + read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); + {_, 3} -> % 3 = group_start + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 4} -> % 4 = group_end + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 5} -> % 5 = bits32 + <<_:32, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) + end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> + read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> + Len = X bsl N + Acc, + NumBytes1 = NumBytes + 1, + <<_:Len/binary, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> + merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Names' -> merge_msg_Names(Prev, New, TrUserData); + 'Package' -> merge_msg_Package(Prev, New, TrUserData) + end. + +-compile({nowarn_unused_function,merge_msg_Names/3}). +merge_msg_Names(#{} = PMsg, + #{repository := NFrepository} = NMsg, TrUserData) -> + S1 = #{repository => NFrepository}, + case {PMsg, NMsg} of + {#{packages := PFpackages}, + #{packages := NFpackages}} -> + S1#{packages => + 'erlang_++'(PFpackages, NFpackages, TrUserData)}; + {_, #{packages := NFpackages}} -> + S1#{packages => NFpackages}; + {#{packages := PFpackages}, _} -> + S1#{packages => PFpackages}; + {_, _} -> S1 + end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{}, #{name := NFname}, _) -> + #{name => NFname}. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> + verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Names' -> v_msg_Names(Msg, [MsgName], TrUserData); + 'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); + _ -> mk_type_error(not_a_known_message, Msg, []) + end. + + +-compile({nowarn_unused_function,v_msg_Names/3}). +v_msg_Names(#{repository := F2} = M, Path, + TrUserData) -> + case M of + #{packages := F1} -> + if is_list(F1) -> + _ = [v_msg_Package(Elem, [packages | Path], TrUserData) + || Elem <- F1], + ok; + true -> + mk_type_error({invalid_list_of, {msg, 'Package'}}, F1, + [packages | Path]) + end; + _ -> ok + end, + v_type_string(F2, [repository | Path], TrUserData), + lists:foreach(fun (packages) -> ok; + (repository) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Names(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [repository] -- maps:keys(M), 'Names'}, + M, Path); +v_msg_Names(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Names'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F1} = M, Path, TrUserData) -> + v_type_string(F1, [name | Path], TrUserData), + lists:foreach(fun (name) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, [name] -- maps:keys(M), + 'Package'}, + M, Path); +v_msg_Package(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) + when is_list(S); is_binary(S) -> + try unicode:characters_to_binary(S) of + B when is_binary(B) -> ok; + {error, _, _} -> + mk_type_error(bad_unicode_string, S, Path) + catch + error:badarg -> + mk_type_error(bad_unicode_string, S, Path) + end; +v_type_string(X, Path, _TrUserData) -> + mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> + Path2 = prettify_path(Path), + erlang:error({gpb_type_error, + {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> + list_to_atom(string:join(lists:map(fun atom_to_list/1, + lists:reverse(PathR)), + ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> + [{{msg, 'Names'}, + [#{name => packages, fnum => 1, rnum => 2, + type => {msg, 'Package'}, occurrence => repeated, + opts => []}, + #{name => repository, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}]}, + {{msg, 'Package'}, + [#{name => name, fnum => 1, rnum => 2, type => string, + occurrence => required, opts => []}]}]. + + +get_msg_names() -> ['Names', 'Package']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Names', 'Package']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> + case find_msg_def(MsgName) of + Fs when is_list(Fs) -> Fs; + error -> erlang:error({no_such_msg, MsgName}) + end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> + erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Names') -> + [#{name => packages, fnum => 1, rnum => 2, + type => {msg, 'Package'}, occurrence => repeated, + opts => []}, + #{name => repository, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}]; +find_msg_def('Package') -> + [#{name => name, fnum => 1, rnum => 2, type => string, + occurrence => required, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> + erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> + erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> + erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> + "4.3.1". + +gpb_version_as_list() -> + [4,3,1]. diff --git a/src/r3_hex_pb_package.erl b/src/r3_hex_pb_package.erl new file mode 100644 index 0000000..131a5da --- /dev/null +++ b/src/r3_hex_pb_package.erl @@ -0,0 +1,1699 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_package). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([enum_symbol_by_value_RetirementReason/1, enum_value_by_symbol_RetirementReason/1]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types +-type 'RetirementReason'() :: 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED'. +-export_type(['RetirementReason'/0]). + +%% message types +-type 'Package'() :: + #{releases => ['Release'()], % = 1 + name => iodata(), % = 2 + repository => iodata() % = 3 + }. + +-type 'Release'() :: + #{version => iodata(), % = 1 + checksum => iodata(), % = 2 + dependencies => ['Dependency'()] % = 3 + %% retired => 'RetirementStatus'() % = 4 + }. + +-type 'RetirementStatus'() :: + #{reason => 'RETIRED_OTHER' | 'RETIRED_INVALID' | 'RETIRED_SECURITY' | 'RETIRED_DEPRECATED' | 'RETIRED_RENAMED' | integer() % = 1, enum RetirementReason + %% message => iodata() % = 2 + }. + +-type 'Dependency'() :: + #{package => iodata(), % = 1 + requirement => iodata() % = 2 + %% optional => boolean() | 0 | 1 % = 3 + %% app => iodata() % = 4 + %% repository => iodata() % = 5 + }. + +-export_type(['Package'/0, 'Release'/0, 'RetirementStatus'/0, 'Dependency'/0]). + +-spec encode_msg('Package'() | 'Release'() | 'RetirementStatus'() | 'Dependency'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> + encode_msg(Msg, MsgName, []). + +-spec encode_msg('Package'() | 'Release'() | 'RetirementStatus'() | 'Dependency'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> + verify_msg(Msg, MsgName, Opts), + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Package' -> + e_msg_Package(id(Msg, TrUserData), TrUserData); + 'Release' -> + e_msg_Release(id(Msg, TrUserData), TrUserData); + 'RetirementStatus' -> + e_msg_RetirementStatus(id(Msg, TrUserData), TrUserData); + 'Dependency' -> + e_msg_Dependency(id(Msg, TrUserData), TrUserData) + end. + + +e_msg_Package(Msg, TrUserData) -> + e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F2, repository := F3} = M, Bin, + TrUserData) -> + B1 = case M of + #{releases := F1} -> + TrF1 = id(F1, TrUserData), + if TrF1 == [] -> Bin; + true -> e_field_Package_releases(TrF1, Bin, TrUserData) + end; + _ -> Bin + end, + B2 = begin + TrF2 = id(F2, TrUserData), + e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) + end, + begin + TrF3 = id(F3, TrUserData), + e_type_string(TrF3, <<B2/binary, 26>>, TrUserData) + end. + +e_msg_Release(Msg, TrUserData) -> + e_msg_Release(Msg, <<>>, TrUserData). + + +e_msg_Release(#{version := F1, checksum := F2} = M, Bin, + TrUserData) -> + B1 = begin + TrF1 = id(F1, TrUserData), + e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) + end, + B2 = begin + TrF2 = id(F2, TrUserData), + e_type_bytes(TrF2, <<B1/binary, 18>>, TrUserData) + end, + B3 = case M of + #{dependencies := F3} -> + TrF3 = id(F3, TrUserData), + if TrF3 == [] -> B2; + true -> + e_field_Release_dependencies(TrF3, B2, TrUserData) + end; + _ -> B2 + end, + case M of + #{retired := F4} -> + begin + TrF4 = id(F4, TrUserData), + e_mfield_Release_retired(TrF4, <<B3/binary, 34>>, + TrUserData) + end; + _ -> B3 + end. + +e_msg_RetirementStatus(Msg, TrUserData) -> + e_msg_RetirementStatus(Msg, <<>>, TrUserData). + + +e_msg_RetirementStatus(#{reason := F1} = M, Bin, + TrUserData) -> + B1 = begin + TrF1 = id(F1, TrUserData), + e_enum_RetirementReason(TrF1, <<Bin/binary, 8>>, + TrUserData) + end, + case M of + #{message := F2} -> + begin + TrF2 = id(F2, TrUserData), + e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) + end; + _ -> B1 + end. + +e_msg_Dependency(Msg, TrUserData) -> + e_msg_Dependency(Msg, <<>>, TrUserData). + + +e_msg_Dependency(#{package := F1, requirement := F2} = + M, + Bin, TrUserData) -> + B1 = begin + TrF1 = id(F1, TrUserData), + e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) + end, + B2 = begin + TrF2 = id(F2, TrUserData), + e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) + end, + B3 = case M of + #{optional := F3} -> + begin + TrF3 = id(F3, TrUserData), + e_type_bool(TrF3, <<B2/binary, 24>>, TrUserData) + end; + _ -> B2 + end, + B4 = case M of + #{app := F4} -> + begin + TrF4 = id(F4, TrUserData), + e_type_string(TrF4, <<B3/binary, 34>>, TrUserData) + end; + _ -> B3 + end, + case M of + #{repository := F5} -> + begin + TrF5 = id(F5, TrUserData), + e_type_string(TrF5, <<B4/binary, 42>>, TrUserData) + end; + _ -> B4 + end. + +e_mfield_Package_releases(Msg, Bin, TrUserData) -> + SubBin = e_msg_Release(Msg, <<>>, TrUserData), + Bin2 = e_varint(byte_size(SubBin), Bin), + <<Bin2/binary, SubBin/binary>>. + +e_field_Package_releases([Elem | Rest], Bin, + TrUserData) -> + Bin2 = <<Bin/binary, 10>>, + Bin3 = e_mfield_Package_releases(id(Elem, TrUserData), + Bin2, TrUserData), + e_field_Package_releases(Rest, Bin3, TrUserData); +e_field_Package_releases([], Bin, _TrUserData) -> Bin. + +e_mfield_Release_dependencies(Msg, Bin, TrUserData) -> + SubBin = e_msg_Dependency(Msg, <<>>, TrUserData), + Bin2 = e_varint(byte_size(SubBin), Bin), + <<Bin2/binary, SubBin/binary>>. + +e_field_Release_dependencies([Elem | Rest], Bin, + TrUserData) -> + Bin2 = <<Bin/binary, 26>>, + Bin3 = e_mfield_Release_dependencies(id(Elem, + TrUserData), + Bin2, TrUserData), + e_field_Release_dependencies(Rest, Bin3, TrUserData); +e_field_Release_dependencies([], Bin, _TrUserData) -> + Bin. + +e_mfield_Release_retired(Msg, Bin, TrUserData) -> + SubBin = e_msg_RetirementStatus(Msg, <<>>, TrUserData), + Bin2 = e_varint(byte_size(SubBin), Bin), + <<Bin2/binary, SubBin/binary>>. + +e_enum_RetirementReason('RETIRED_OTHER', Bin, + _TrUserData) -> + <<Bin/binary, 0>>; +e_enum_RetirementReason('RETIRED_INVALID', Bin, + _TrUserData) -> + <<Bin/binary, 1>>; +e_enum_RetirementReason('RETIRED_SECURITY', Bin, + _TrUserData) -> + <<Bin/binary, 2>>; +e_enum_RetirementReason('RETIRED_DEPRECATED', Bin, + _TrUserData) -> + <<Bin/binary, 3>>; +e_enum_RetirementReason('RETIRED_RENAMED', Bin, + _TrUserData) -> + <<Bin/binary, 4>>; +e_enum_RetirementReason(V, Bin, _TrUserData) -> + e_varint(V, Bin). + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> + e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> + e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> + <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> + <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> + Utf8 = unicode:characters_to_binary(S), + Bin2 = e_varint(byte_size(Utf8), Bin), + <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) + when is_binary(Bytes) -> + Bin2 = e_varint(byte_size(Bytes), Bin), + <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) + when is_list(Bytes) -> + BytesBin = iolist_to_binary(Bytes), + Bin2 = e_varint(byte_size(BytesBin), Bin), + <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> + <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> + <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> + <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> + <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> + <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> + <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> + Bin2 = <<Bin/binary, (N band 127 bor 128)>>, + e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> + decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> + TrUserData = proplists:get_value(user_data, Opts), + decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason -> + StackTrace = erlang:get_stacktrace(), + error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-endif. + +-endif. + +decode_msg_2_doit('Package', Bin, TrUserData) -> + id(d_msg_Package(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Release', Bin, TrUserData) -> + id(d_msg_Release(Bin, TrUserData), TrUserData); +decode_msg_2_doit('RetirementStatus', Bin, + TrUserData) -> + id(d_msg_RetirementStatus(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Dependency', Bin, TrUserData) -> + id(d_msg_Dependency(Bin, TrUserData), TrUserData). + + + +d_msg_Package(Bin, TrUserData) -> + dfp_read_field_def_Package(Bin, 0, 0, + id([], TrUserData), id('$undef', TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_releases(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_name(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<26, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_repository(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, R1, F@_2, F@_3, + TrUserData) -> + S1 = #{name => F@_2, repository => F@_3}, + if R1 == '$undef' -> S1; + true -> S1#{releases => lists_reverse(R1, TrUserData)} + end; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData) -> + dg_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Package_releases(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 18 -> + d_field_Package_name(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 26 -> + d_field_Package_repository(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 1 -> + skip_64_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 2 -> + skip_length_delimited_Package(Rest, 0, 0, F@_1, F@_2, + F@_3, TrUserData); + 3 -> + skip_group_Package(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, + TrUserData); + 5 -> + skip_32_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData) + end + end; +dg_read_field_def_Package(<<>>, 0, 0, R1, F@_2, F@_3, + TrUserData) -> + S1 = #{name => F@_2, repository => F@_3}, + if R1 == '$undef' -> S1; + true -> S1#{releases => lists_reverse(R1, TrUserData)} + end. + +d_field_Package_releases(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_releases(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +d_field_Package_releases(<<0:1, X:7, Rest/binary>>, N, + Acc, Prev, F@_2, F@_3, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bs:Len/binary, Rest2/binary>> = Rest, + {id(d_msg_Release(Bs, TrUserData), TrUserData), + Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, + cons(NewFValue, Prev, TrUserData), F@_2, F@_3, + TrUserData). + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, + F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, + F@_2, F@_3, TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, + F@_1, _, F@_3, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, F@_1, NewFValue, + F@_3, TrUserData). + +d_field_Package_repository(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_repository(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +d_field_Package_repository(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, F@_1, F@_2, + NewFValue, TrUserData). + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + skip_varint_Package(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + skip_length_delimited_Package(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, + F@_3, TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, F@_2, F@_3, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Package(Rest, 0, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +d_msg_Release(Bin, TrUserData) -> + dfp_read_field_def_Release(Bin, 0, 0, + id('$undef', TrUserData), + id('$undef', TrUserData), id([], TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Release(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + d_field_Release_version(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, TrUserData); +dfp_read_field_def_Release(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + d_field_Release_checksum(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, TrUserData); +dfp_read_field_def_Release(<<26, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + d_field_Release_dependencies(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData); +dfp_read_field_def_Release(<<34, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + d_field_Release_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, TrUserData); +dfp_read_field_def_Release(<<>>, 0, 0, F@_1, F@_2, R1, + F@_4, TrUserData) -> + S1 = #{version => F@_1, checksum => F@_2}, + S2 = if R1 == '$undef' -> S1; + true -> + S1#{dependencies => lists_reverse(R1, TrUserData)} + end, + if F@_4 == '$undef' -> S2; + true -> S2#{retired => F@_4} + end; +dfp_read_field_def_Release(Other, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData) -> + dg_read_field_def_Release(Other, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +dg_read_field_def_Release(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Release(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, TrUserData); +dg_read_field_def_Release(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Release_version(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, TrUserData); + 18 -> + d_field_Release_checksum(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, TrUserData); + 26 -> + d_field_Release_dependencies(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, TrUserData); + 34 -> + d_field_Release_retired(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, + TrUserData); + 1 -> + skip_64_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, + TrUserData); + 2 -> + skip_length_delimited_Release(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, TrUserData); + 3 -> + skip_group_Release(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, + F@_4, TrUserData); + 5 -> + skip_32_Release(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, + TrUserData) + end + end; +dg_read_field_def_Release(<<>>, 0, 0, F@_1, F@_2, R1, + F@_4, TrUserData) -> + S1 = #{version => F@_1, checksum => F@_2}, + S2 = if R1 == '$undef' -> S1; + true -> + S1#{dependencies => lists_reverse(R1, TrUserData)} + end, + if F@_4 == '$undef' -> S2; + true -> S2#{retired => F@_4} + end. + +d_field_Release_version(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 57 -> + d_field_Release_version(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_version(<<0:1, X:7, Rest/binary>>, N, + Acc, _, F@_2, F@_3, F@_4, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Release(RestF, 0, 0, NewFValue, F@_2, + F@_3, F@_4, TrUserData). + +d_field_Release_checksum(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 57 -> + d_field_Release_checksum(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_checksum(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, _, F@_3, F@_4, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Release(RestF, 0, 0, F@_1, NewFValue, + F@_3, F@_4, TrUserData). + +d_field_Release_dependencies(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 57 -> + d_field_Release_dependencies(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_dependencies(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, Prev, F@_4, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bs:Len/binary, Rest2/binary>> = Rest, + {id(d_msg_Dependency(Bs, TrUserData), TrUserData), + Rest2} + end, + dfp_read_field_def_Release(RestF, 0, 0, F@_1, F@_2, + cons(NewFValue, Prev, TrUserData), F@_4, + TrUserData). + +d_field_Release_retired(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 57 -> + d_field_Release_retired(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, TrUserData); +d_field_Release_retired(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, Prev, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bs:Len/binary, Rest2/binary>> = Rest, + {id(d_msg_RetirementStatus(Bs, TrUserData), + TrUserData), + Rest2} + end, + dfp_read_field_def_Release(RestF, 0, 0, F@_1, F@_2, + F@_3, + if Prev == '$undef' -> NewFValue; + true -> + merge_msg_RetirementStatus(Prev, + NewFValue, + TrUserData) + end, + TrUserData). + +skip_varint_Release(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + skip_varint_Release(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, TrUserData); +skip_varint_Release(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, F@_4, TrUserData) -> + dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +skip_length_delimited_Release(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) + when N < 57 -> + skip_length_delimited_Release(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, + TrUserData); +skip_length_delimited_Release(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Release(Rest2, 0, 0, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +skip_group_Release(Bin, FNum, Z2, F@_1, F@_2, F@_3, + F@_4, TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Release(Rest, 0, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +skip_32_Release(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, F@_4, TrUserData) -> + dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +skip_64_Release(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, F@_4, TrUserData) -> + dfp_read_field_def_Release(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, TrUserData). + +d_msg_RetirementStatus(Bin, TrUserData) -> + dfp_read_field_def_RetirementStatus(Bin, 0, 0, + id('$undef', TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_RetirementStatus(<<8, Rest/binary>>, + Z1, Z2, F@_1, F@_2, TrUserData) -> + d_field_RetirementStatus_reason(Rest, Z1, Z2, F@_1, + F@_2, TrUserData); +dfp_read_field_def_RetirementStatus(<<18, Rest/binary>>, + Z1, Z2, F@_1, F@_2, TrUserData) -> + d_field_RetirementStatus_message(Rest, Z1, Z2, F@_1, + F@_2, TrUserData); +dfp_read_field_def_RetirementStatus(<<>>, 0, 0, F@_1, + F@_2, _) -> + S1 = #{reason => F@_1}, + if F@_2 == '$undef' -> S1; + true -> S1#{message => F@_2} + end; +dfp_read_field_def_RetirementStatus(Other, Z1, Z2, F@_1, + F@_2, TrUserData) -> + dg_read_field_def_RetirementStatus(Other, Z1, Z2, F@_1, + F@_2, TrUserData). + +dg_read_field_def_RetirementStatus(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_RetirementStatus(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, TrUserData); +dg_read_field_def_RetirementStatus(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 8 -> + d_field_RetirementStatus_reason(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 18 -> + d_field_RetirementStatus_message(Rest, 0, 0, F@_1, F@_2, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_RetirementStatus(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 1 -> + skip_64_RetirementStatus(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 2 -> + skip_length_delimited_RetirementStatus(Rest, 0, 0, F@_1, + F@_2, TrUserData); + 3 -> + skip_group_RetirementStatus(Rest, Key bsr 3, 0, F@_1, + F@_2, TrUserData); + 5 -> + skip_32_RetirementStatus(Rest, 0, 0, F@_1, F@_2, + TrUserData) + end + end; +dg_read_field_def_RetirementStatus(<<>>, 0, 0, F@_1, + F@_2, _) -> + S1 = #{reason => F@_1}, + if F@_2 == '$undef' -> S1; + true -> S1#{message => F@_2} + end. + +d_field_RetirementStatus_reason(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_RetirementStatus_reason(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, TrUserData); +d_field_RetirementStatus_reason(<<0:1, X:7, + Rest/binary>>, + N, Acc, _, F@_2, TrUserData) -> + {NewFValue, RestF} = {id(d_enum_RetirementReason(begin + <<Res:32/signed-native>> = + <<(X bsl N + + Acc):32/unsigned-native>>, + id(Res, TrUserData) + end), + TrUserData), + Rest}, + dfp_read_field_def_RetirementStatus(RestF, 0, 0, + NewFValue, F@_2, TrUserData). + +d_field_RetirementStatus_message(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_RetirementStatus_message(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, TrUserData); +d_field_RetirementStatus_message(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_RetirementStatus(RestF, 0, 0, F@_1, + NewFValue, TrUserData). + +skip_varint_RetirementStatus(<<1:1, _:7, Rest/binary>>, + Z1, Z2, F@_1, F@_2, TrUserData) -> + skip_varint_RetirementStatus(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +skip_varint_RetirementStatus(<<0:1, _:7, Rest/binary>>, + Z1, Z2, F@_1, F@_2, TrUserData) -> + dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, + F@_2, TrUserData). + +skip_length_delimited_RetirementStatus(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + skip_length_delimited_RetirementStatus(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, + TrUserData); +skip_length_delimited_RetirementStatus(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_RetirementStatus(Rest2, 0, 0, F@_1, + F@_2, TrUserData). + +skip_group_RetirementStatus(Bin, FNum, Z2, F@_1, F@_2, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_RetirementStatus(Rest, 0, Z2, F@_1, + F@_2, TrUserData). + +skip_32_RetirementStatus(<<_:32, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, + F@_2, TrUserData). + +skip_64_RetirementStatus(<<_:64, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + dfp_read_field_def_RetirementStatus(Rest, Z1, Z2, F@_1, + F@_2, TrUserData). + +d_msg_Dependency(Bin, TrUserData) -> + dfp_read_field_def_Dependency(Bin, 0, 0, + id('$undef', TrUserData), + id('$undef', TrUserData), + id('$undef', TrUserData), + id('$undef', TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Dependency(<<10, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + d_field_Dependency_package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<18, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + d_field_Dependency_requirement(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<24, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + d_field_Dependency_optional(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<34, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + d_field_Dependency_app(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<42, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + d_field_Dependency_repository(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); +dfp_read_field_def_Dependency(<<>>, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, _) -> + S1 = #{package => F@_1, requirement => F@_2}, + S2 = if F@_3 == '$undef' -> S1; + true -> S1#{optional => F@_3} + end, + S3 = if F@_4 == '$undef' -> S2; + true -> S2#{app => F@_4} + end, + if F@_5 == '$undef' -> S3; + true -> S3#{repository => F@_5} + end; +dfp_read_field_def_Dependency(Other, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData) -> + dg_read_field_def_Dependency(Other, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +dg_read_field_def_Dependency(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Dependency(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +dg_read_field_def_Dependency(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, + TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Dependency_package(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData); + 18 -> + d_field_Dependency_requirement(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); + 24 -> + d_field_Dependency_optional(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); + 34 -> + d_field_Dependency_app(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData); + 42 -> + d_field_Dependency_repository(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData); + 1 -> + skip_64_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, + F@_5, TrUserData); + 2 -> + skip_length_delimited_Dependency(Rest, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); + 3 -> + skip_group_Dependency(Rest, Key bsr 3, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData); + 5 -> + skip_32_Dependency(Rest, 0, 0, F@_1, F@_2, F@_3, F@_4, + F@_5, TrUserData) + end + end; +dg_read_field_def_Dependency(<<>>, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, _) -> + S1 = #{package => F@_1, requirement => F@_2}, + S2 = if F@_3 == '$undef' -> S1; + true -> S1#{optional => F@_3} + end, + S3 = if F@_4 == '$undef' -> S2; + true -> S2#{app => F@_4} + end, + if F@_5 == '$undef' -> S3; + true -> S3#{repository => F@_5} + end. + +d_field_Dependency_package(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 57 -> + d_field_Dependency_package(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_package(<<0:1, X:7, Rest/binary>>, N, + Acc, _, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Dependency(RestF, 0, 0, NewFValue, + F@_2, F@_3, F@_4, F@_5, TrUserData). + +d_field_Dependency_requirement(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 57 -> + d_field_Dependency_requirement(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, + TrUserData); +d_field_Dependency_requirement(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, _, F@_3, F@_4, F@_5, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, + NewFValue, F@_3, F@_4, F@_5, TrUserData). + +d_field_Dependency_optional(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 57 -> + d_field_Dependency_optional(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_optional(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, _, F@_4, F@_5, TrUserData) -> + {NewFValue, RestF} = {id(X bsl N + Acc =/= 0, + TrUserData), + Rest}, + dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, + NewFValue, F@_4, F@_5, TrUserData). + +d_field_Dependency_app(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 57 -> + d_field_Dependency_app(Rest, N + 7, X bsl N + Acc, F@_1, + F@_2, F@_3, F@_4, F@_5, TrUserData); +d_field_Dependency_app(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, _, F@_5, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, + F@_3, NewFValue, F@_5, TrUserData). + +d_field_Dependency_repository(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) + when N < 57 -> + d_field_Dependency_repository(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, F@_5, + TrUserData); +d_field_Dependency_repository(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Dependency(RestF, 0, 0, F@_1, F@_2, + F@_3, F@_4, NewFValue, TrUserData). + +skip_varint_Dependency(<<1:1, _:7, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + skip_varint_Dependency(Rest, Z1, Z2, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData); +skip_varint_Dependency(<<0:1, _:7, Rest/binary>>, Z1, + Z2, F@_1, F@_2, F@_3, F@_4, F@_5, TrUserData) -> + dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +skip_length_delimited_Dependency(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, + TrUserData) + when N < 57 -> + skip_length_delimited_Dependency(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, F@_4, + F@_5, TrUserData); +skip_length_delimited_Dependency(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, F@_4, F@_5, + TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Dependency(Rest2, 0, 0, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +skip_group_Dependency(Bin, FNum, Z2, F@_1, F@_2, F@_3, + F@_4, F@_5, TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Dependency(Rest, 0, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +skip_32_Dependency(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, F@_4, F@_5, TrUserData) -> + dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +skip_64_Dependency(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, F@_4, F@_5, TrUserData) -> + dfp_read_field_def_Dependency(Rest, Z1, Z2, F@_1, F@_2, + F@_3, F@_4, F@_5, TrUserData). + +d_enum_RetirementReason(0) -> 'RETIRED_OTHER'; +d_enum_RetirementReason(1) -> 'RETIRED_INVALID'; +d_enum_RetirementReason(2) -> 'RETIRED_SECURITY'; +d_enum_RetirementReason(3) -> 'RETIRED_DEPRECATED'; +d_enum_RetirementReason(4) -> 'RETIRED_RENAMED'; +d_enum_RetirementReason(V) -> V. + +read_group(Bin, FieldNum) -> + {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), + <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, + {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) + when N < (32-7) -> + read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, + FieldNum) -> + Key = X bsl N + Acc, + TagLen1 = TagLen + 1, + case {Key bsr 3, Key band 7} of + {FieldNum, 4} -> % 4 = group_end + {NumBytes, TagLen1}; + {_, 0} -> % 0 = varint + read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); + {_, 1} -> % 1 = bits64 + <<_:64, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); + {_, 2} -> % 2 = length_delimited + read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); + {_, 3} -> % 3 = group_start + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 4} -> % 4 = group_end + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 5} -> % 5 = bits32 + <<_:32, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) + end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> + read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> + Len = X bsl N + Acc, + NumBytes1 = NumBytes + 1, + <<_:Len/binary, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> + merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Package' -> merge_msg_Package(Prev, New, TrUserData); + 'Release' -> merge_msg_Release(Prev, New, TrUserData); + 'RetirementStatus' -> + merge_msg_RetirementStatus(Prev, New, TrUserData); + 'Dependency' -> + merge_msg_Dependency(Prev, New, TrUserData) + end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{} = PMsg, + #{name := NFname, repository := NFrepository} = NMsg, + TrUserData) -> + S1 = #{name => NFname, repository => NFrepository}, + case {PMsg, NMsg} of + {#{releases := PFreleases}, + #{releases := NFreleases}} -> + S1#{releases => + 'erlang_++'(PFreleases, NFreleases, TrUserData)}; + {_, #{releases := NFreleases}} -> + S1#{releases => NFreleases}; + {#{releases := PFreleases}, _} -> + S1#{releases => PFreleases}; + {_, _} -> S1 + end. + +-compile({nowarn_unused_function,merge_msg_Release/3}). +merge_msg_Release(#{} = PMsg, + #{version := NFversion, checksum := NFchecksum} = NMsg, + TrUserData) -> + S1 = #{version => NFversion, checksum => NFchecksum}, + S2 = case {PMsg, NMsg} of + {#{dependencies := PFdependencies}, + #{dependencies := NFdependencies}} -> + S1#{dependencies => + 'erlang_++'(PFdependencies, NFdependencies, + TrUserData)}; + {_, #{dependencies := NFdependencies}} -> + S1#{dependencies => NFdependencies}; + {#{dependencies := PFdependencies}, _} -> + S1#{dependencies => PFdependencies}; + {_, _} -> S1 + end, + case {PMsg, NMsg} of + {#{retired := PFretired}, #{retired := NFretired}} -> + S2#{retired => + merge_msg_RetirementStatus(PFretired, NFretired, + TrUserData)}; + {_, #{retired := NFretired}} -> + S2#{retired => NFretired}; + {#{retired := PFretired}, _} -> + S2#{retired => PFretired}; + {_, _} -> S2 + end. + +-compile({nowarn_unused_function,merge_msg_RetirementStatus/3}). +merge_msg_RetirementStatus(#{} = PMsg, + #{reason := NFreason} = NMsg, _) -> + S1 = #{reason => NFreason}, + case {PMsg, NMsg} of + {_, #{message := NFmessage}} -> + S1#{message => NFmessage}; + {#{message := PFmessage}, _} -> + S1#{message => PFmessage}; + _ -> S1 + end. + +-compile({nowarn_unused_function,merge_msg_Dependency/3}). +merge_msg_Dependency(#{} = PMsg, + #{package := NFpackage, requirement := NFrequirement} = + NMsg, + _) -> + S1 = #{package => NFpackage, + requirement => NFrequirement}, + S2 = case {PMsg, NMsg} of + {_, #{optional := NFoptional}} -> + S1#{optional => NFoptional}; + {#{optional := PFoptional}, _} -> + S1#{optional => PFoptional}; + _ -> S1 + end, + S3 = case {PMsg, NMsg} of + {_, #{app := NFapp}} -> S2#{app => NFapp}; + {#{app := PFapp}, _} -> S2#{app => PFapp}; + _ -> S2 + end, + case {PMsg, NMsg} of + {_, #{repository := NFrepository}} -> + S3#{repository => NFrepository}; + {#{repository := PFrepository}, _} -> + S3#{repository => PFrepository}; + _ -> S3 + end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> + verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); + 'Release' -> v_msg_Release(Msg, [MsgName], TrUserData); + 'RetirementStatus' -> + v_msg_RetirementStatus(Msg, [MsgName], TrUserData); + 'Dependency' -> + v_msg_Dependency(Msg, [MsgName], TrUserData); + _ -> mk_type_error(not_a_known_message, Msg, []) + end. + + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F2, repository := F3} = M, Path, + TrUserData) -> + case M of + #{releases := F1} -> + if is_list(F1) -> + _ = [v_msg_Release(Elem, [releases | Path], TrUserData) + || Elem <- F1], + ok; + true -> + mk_type_error({invalid_list_of, {msg, 'Release'}}, F1, + [releases | Path]) + end; + _ -> ok + end, + v_type_string(F2, [name | Path], TrUserData), + v_type_string(F3, [repository | Path], TrUserData), + lists:foreach(fun (releases) -> ok; + (name) -> ok; + (repository) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [name, repository] -- maps:keys(M), 'Package'}, + M, Path); +v_msg_Package(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Release/3}). +v_msg_Release(#{version := F1, checksum := F2} = M, + Path, TrUserData) -> + v_type_string(F1, [version | Path], TrUserData), + v_type_bytes(F2, [checksum | Path], TrUserData), + case M of + #{dependencies := F3} -> + if is_list(F3) -> + _ = [v_msg_Dependency(Elem, [dependencies | Path], + TrUserData) + || Elem <- F3], + ok; + true -> + mk_type_error({invalid_list_of, {msg, 'Dependency'}}, + F3, [dependencies | Path]) + end; + _ -> ok + end, + case M of + #{retired := F4} -> + v_msg_RetirementStatus(F4, [retired | Path], + TrUserData); + _ -> ok + end, + lists:foreach(fun (version) -> ok; + (checksum) -> ok; + (dependencies) -> ok; + (retired) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Release(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [version, checksum] -- maps:keys(M), 'Release'}, + M, Path); +v_msg_Release(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Release'}, X, Path). + +-compile({nowarn_unused_function,v_msg_RetirementStatus/3}). +v_msg_RetirementStatus(#{reason := F1} = M, Path, + TrUserData) -> + v_enum_RetirementReason(F1, [reason | Path], + TrUserData), + case M of + #{message := F2} -> + v_type_string(F2, [message | Path], TrUserData); + _ -> ok + end, + lists:foreach(fun (reason) -> ok; + (message) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_RetirementStatus(M, Path, _TrUserData) + when is_map(M) -> + mk_type_error({missing_fields, [reason] -- maps:keys(M), + 'RetirementStatus'}, + M, Path); +v_msg_RetirementStatus(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'RetirementStatus'}, X, + Path). + +-compile({nowarn_unused_function,v_msg_Dependency/3}). +v_msg_Dependency(#{package := F1, requirement := F2} = + M, + Path, TrUserData) -> + v_type_string(F1, [package | Path], TrUserData), + v_type_string(F2, [requirement | Path], TrUserData), + case M of + #{optional := F3} -> + v_type_bool(F3, [optional | Path], TrUserData); + _ -> ok + end, + case M of + #{app := F4} -> + v_type_string(F4, [app | Path], TrUserData); + _ -> ok + end, + case M of + #{repository := F5} -> + v_type_string(F5, [repository | Path], TrUserData); + _ -> ok + end, + lists:foreach(fun (package) -> ok; + (requirement) -> ok; + (optional) -> ok; + (app) -> ok; + (repository) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Dependency(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [package, requirement] -- maps:keys(M), 'Dependency'}, + M, Path); +v_msg_Dependency(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Dependency'}, X, Path). + +-compile({nowarn_unused_function,v_enum_RetirementReason/3}). +v_enum_RetirementReason('RETIRED_OTHER', _Path, + _TrUserData) -> + ok; +v_enum_RetirementReason('RETIRED_INVALID', _Path, + _TrUserData) -> + ok; +v_enum_RetirementReason('RETIRED_SECURITY', _Path, + _TrUserData) -> + ok; +v_enum_RetirementReason('RETIRED_DEPRECATED', _Path, + _TrUserData) -> + ok; +v_enum_RetirementReason('RETIRED_RENAMED', _Path, + _TrUserData) -> + ok; +v_enum_RetirementReason(V, Path, TrUserData) + when is_integer(V) -> + v_type_sint32(V, Path, TrUserData); +v_enum_RetirementReason(X, Path, _TrUserData) -> + mk_type_error({invalid_enum, 'RetirementReason'}, X, + Path). + +-compile({nowarn_unused_function,v_type_sint32/3}). +v_type_sint32(N, _Path, _TrUserData) + when -2147483648 =< N, N =< 2147483647 -> + ok; +v_type_sint32(N, Path, _TrUserData) + when is_integer(N) -> + mk_type_error({value_out_of_range, sint32, signed, 32}, + N, Path); +v_type_sint32(X, Path, _TrUserData) -> + mk_type_error({bad_integer, sint32, signed, 32}, X, + Path). + +-compile({nowarn_unused_function,v_type_bool/3}). +v_type_bool(false, _Path, _TrUserData) -> ok; +v_type_bool(true, _Path, _TrUserData) -> ok; +v_type_bool(0, _Path, _TrUserData) -> ok; +v_type_bool(1, _Path, _TrUserData) -> ok; +v_type_bool(X, Path, _TrUserData) -> + mk_type_error(bad_boolean_value, X, Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) + when is_list(S); is_binary(S) -> + try unicode:characters_to_binary(S) of + B when is_binary(B) -> ok; + {error, _, _} -> + mk_type_error(bad_unicode_string, S, Path) + catch + error:badarg -> + mk_type_error(bad_unicode_string, S, Path) + end; +v_type_string(X, Path, _TrUserData) -> + mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,v_type_bytes/3}). +v_type_bytes(B, _Path, _TrUserData) when is_binary(B) -> + ok; +v_type_bytes(B, _Path, _TrUserData) when is_list(B) -> + ok; +v_type_bytes(X, Path, _TrUserData) -> + mk_type_error(bad_binary_value, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> + Path2 = prettify_path(Path), + erlang:error({gpb_type_error, + {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> + list_to_atom(string:join(lists:map(fun atom_to_list/1, + lists:reverse(PathR)), + ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> + [{{enum, 'RetirementReason'}, + [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, + {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, + {'RETIRED_RENAMED', 4}]}, + {{msg, 'Package'}, + [#{name => releases, fnum => 1, rnum => 2, + type => {msg, 'Release'}, occurrence => repeated, + opts => []}, + #{name => name, fnum => 2, rnum => 3, type => string, + occurrence => required, opts => []}, + #{name => repository, fnum => 3, rnum => 4, + type => string, occurrence => required, opts => []}]}, + {{msg, 'Release'}, + [#{name => version, fnum => 1, rnum => 2, + type => string, occurrence => required, opts => []}, + #{name => checksum, fnum => 2, rnum => 3, type => bytes, + occurrence => required, opts => []}, + #{name => dependencies, fnum => 3, rnum => 4, + type => {msg, 'Dependency'}, occurrence => repeated, + opts => []}, + #{name => retired, fnum => 4, rnum => 5, + type => {msg, 'RetirementStatus'}, + occurrence => optional, opts => []}]}, + {{msg, 'RetirementStatus'}, + [#{name => reason, fnum => 1, rnum => 2, + type => {enum, 'RetirementReason'}, + occurrence => required, opts => []}, + #{name => message, fnum => 2, rnum => 3, type => string, + occurrence => optional, opts => []}]}, + {{msg, 'Dependency'}, + [#{name => package, fnum => 1, rnum => 2, + type => string, occurrence => required, opts => []}, + #{name => requirement, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}, + #{name => optional, fnum => 3, rnum => 4, type => bool, + occurrence => optional, opts => []}, + #{name => app, fnum => 4, rnum => 5, type => string, + occurrence => optional, opts => []}, + #{name => repository, fnum => 5, rnum => 6, + type => string, occurrence => optional, opts => []}]}]. + + +get_msg_names() -> + ['Package', 'Release', 'RetirementStatus', + 'Dependency']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> + ['Package', 'Release', 'RetirementStatus', + 'Dependency']. + + +get_enum_names() -> ['RetirementReason']. + + +fetch_msg_def(MsgName) -> + case find_msg_def(MsgName) of + Fs when is_list(Fs) -> Fs; + error -> erlang:error({no_such_msg, MsgName}) + end. + + +fetch_enum_def(EnumName) -> + case find_enum_def(EnumName) of + Es when is_list(Es) -> Es; + error -> erlang:error({no_such_enum, EnumName}) + end. + + +find_msg_def('Package') -> + [#{name => releases, fnum => 1, rnum => 2, + type => {msg, 'Release'}, occurrence => repeated, + opts => []}, + #{name => name, fnum => 2, rnum => 3, type => string, + occurrence => required, opts => []}, + #{name => repository, fnum => 3, rnum => 4, + type => string, occurrence => required, opts => []}]; +find_msg_def('Release') -> + [#{name => version, fnum => 1, rnum => 2, + type => string, occurrence => required, opts => []}, + #{name => checksum, fnum => 2, rnum => 3, type => bytes, + occurrence => required, opts => []}, + #{name => dependencies, fnum => 3, rnum => 4, + type => {msg, 'Dependency'}, occurrence => repeated, + opts => []}, + #{name => retired, fnum => 4, rnum => 5, + type => {msg, 'RetirementStatus'}, + occurrence => optional, opts => []}]; +find_msg_def('RetirementStatus') -> + [#{name => reason, fnum => 1, rnum => 2, + type => {enum, 'RetirementReason'}, + occurrence => required, opts => []}, + #{name => message, fnum => 2, rnum => 3, type => string, + occurrence => optional, opts => []}]; +find_msg_def('Dependency') -> + [#{name => package, fnum => 1, rnum => 2, + type => string, occurrence => required, opts => []}, + #{name => requirement, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}, + #{name => optional, fnum => 3, rnum => 4, type => bool, + occurrence => optional, opts => []}, + #{name => app, fnum => 4, rnum => 5, type => string, + occurrence => optional, opts => []}, + #{name => repository, fnum => 5, rnum => 6, + type => string, occurrence => optional, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def('RetirementReason') -> + [{'RETIRED_OTHER', 0}, {'RETIRED_INVALID', 1}, + {'RETIRED_SECURITY', 2}, {'RETIRED_DEPRECATED', 3}, + {'RETIRED_RENAMED', 4}]; +find_enum_def(_) -> error. + + +enum_symbol_by_value('RetirementReason', Value) -> + enum_symbol_by_value_RetirementReason(Value). + + +enum_value_by_symbol('RetirementReason', Sym) -> + enum_value_by_symbol_RetirementReason(Sym). + + +enum_symbol_by_value_RetirementReason(0) -> + 'RETIRED_OTHER'; +enum_symbol_by_value_RetirementReason(1) -> + 'RETIRED_INVALID'; +enum_symbol_by_value_RetirementReason(2) -> + 'RETIRED_SECURITY'; +enum_symbol_by_value_RetirementReason(3) -> + 'RETIRED_DEPRECATED'; +enum_symbol_by_value_RetirementReason(4) -> + 'RETIRED_RENAMED'. + + +enum_value_by_symbol_RetirementReason('RETIRED_OTHER') -> + 0; +enum_value_by_symbol_RetirementReason('RETIRED_INVALID') -> + 1; +enum_value_by_symbol_RetirementReason('RETIRED_SECURITY') -> + 2; +enum_value_by_symbol_RetirementReason('RETIRED_DEPRECATED') -> + 3; +enum_value_by_symbol_RetirementReason('RETIRED_RENAMED') -> + 4. + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> + erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> + "4.3.1". + +gpb_version_as_list() -> + [4,3,1]. diff --git a/src/r3_hex_pb_signed.erl b/src/r3_hex_pb_signed.erl new file mode 100644 index 0000000..e8b188a --- /dev/null +++ b/src/r3_hex_pb_signed.erl @@ -0,0 +1,564 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_signed). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Signed'() :: + #{payload => iodata() % = 1 + %% signature => iodata() % = 2 + }. + +-export_type(['Signed'/0]). + +-spec encode_msg('Signed'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> + encode_msg(Msg, MsgName, []). + +-spec encode_msg('Signed'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> + verify_msg(Msg, MsgName, Opts), + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Signed' -> + e_msg_Signed(id(Msg, TrUserData), TrUserData) + end. + + +e_msg_Signed(Msg, TrUserData) -> + e_msg_Signed(Msg, <<>>, TrUserData). + + +e_msg_Signed(#{payload := F1} = M, Bin, TrUserData) -> + B1 = begin + TrF1 = id(F1, TrUserData), + e_type_bytes(TrF1, <<Bin/binary, 10>>, TrUserData) + end, + case M of + #{signature := F2} -> + begin + TrF2 = id(F2, TrUserData), + e_type_bytes(TrF2, <<B1/binary, 18>>, TrUserData) + end; + _ -> B1 + end. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> + e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> + e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> + <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> + <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> + Utf8 = unicode:characters_to_binary(S), + Bin2 = e_varint(byte_size(Utf8), Bin), + <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) + when is_binary(Bytes) -> + Bin2 = e_varint(byte_size(Bytes), Bin), + <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) + when is_list(Bytes) -> + BytesBin = iolist_to_binary(Bytes), + Bin2 = e_varint(byte_size(BytesBin), Bin), + <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> + <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> + <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> + <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> + <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> + <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> + <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> + Bin2 = <<Bin/binary, (N band 127 bor 128)>>, + e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> + decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> + TrUserData = proplists:get_value(user_data, Opts), + decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason -> + StackTrace = erlang:get_stacktrace(), + error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-endif. + +-endif. + +decode_msg_2_doit('Signed', Bin, TrUserData) -> + id(d_msg_Signed(Bin, TrUserData), TrUserData). + + + +d_msg_Signed(Bin, TrUserData) -> + dfp_read_field_def_Signed(Bin, 0, 0, + id('$undef', TrUserData), + id('$undef', TrUserData), TrUserData). + +dfp_read_field_def_Signed(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Signed_payload(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Signed(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Signed_signature(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Signed(<<>>, 0, 0, F@_1, F@_2, _) -> + S1 = #{payload => F@_1}, + if F@_2 == '$undef' -> S1; + true -> S1#{signature => F@_2} + end; +dfp_read_field_def_Signed(Other, Z1, Z2, F@_1, F@_2, + TrUserData) -> + dg_read_field_def_Signed(Other, Z1, Z2, F@_1, F@_2, + TrUserData). + +dg_read_field_def_Signed(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Signed(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +dg_read_field_def_Signed(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Signed_payload(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 18 -> + d_field_Signed_signature(Rest, 0, 0, F@_1, F@_2, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData); + 1 -> skip_64_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData); + 2 -> + skip_length_delimited_Signed(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 3 -> + skip_group_Signed(Rest, Key bsr 3, 0, F@_1, F@_2, + TrUserData); + 5 -> skip_32_Signed(Rest, 0, 0, F@_1, F@_2, TrUserData) + end + end; +dg_read_field_def_Signed(<<>>, 0, 0, F@_1, F@_2, _) -> + S1 = #{payload => F@_1}, + if F@_2 == '$undef' -> S1; + true -> S1#{signature => F@_2} + end. + +d_field_Signed_payload(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Signed_payload(Rest, N + 7, X bsl N + Acc, F@_1, + F@_2, TrUserData); +d_field_Signed_payload(<<0:1, X:7, Rest/binary>>, N, + Acc, _, F@_2, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Signed(RestF, 0, 0, NewFValue, F@_2, + TrUserData). + +d_field_Signed_signature(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Signed_signature(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +d_field_Signed_signature(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Signed(RestF, 0, 0, F@_1, NewFValue, + TrUserData). + +skip_varint_Signed(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + skip_varint_Signed(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +skip_varint_Signed(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_length_delimited_Signed(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + skip_length_delimited_Signed(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +skip_length_delimited_Signed(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Signed(Rest2, 0, 0, F@_1, F@_2, + TrUserData). + +skip_group_Signed(Bin, FNum, Z2, F@_1, F@_2, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Signed(Rest, 0, Z2, F@_1, F@_2, + TrUserData). + +skip_32_Signed(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, TrUserData) -> + dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_64_Signed(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, TrUserData) -> + dfp_read_field_def_Signed(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +read_group(Bin, FieldNum) -> + {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), + <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, + {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) + when N < (32-7) -> + read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, + FieldNum) -> + Key = X bsl N + Acc, + TagLen1 = TagLen + 1, + case {Key bsr 3, Key band 7} of + {FieldNum, 4} -> % 4 = group_end + {NumBytes, TagLen1}; + {_, 0} -> % 0 = varint + read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); + {_, 1} -> % 1 = bits64 + <<_:64, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); + {_, 2} -> % 2 = length_delimited + read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); + {_, 3} -> % 3 = group_start + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 4} -> % 4 = group_end + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 5} -> % 5 = bits32 + <<_:32, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) + end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> + read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> + Len = X bsl N + Acc, + NumBytes1 = NumBytes + 1, + <<_:Len/binary, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> + merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Signed' -> merge_msg_Signed(Prev, New, TrUserData) + end. + +-compile({nowarn_unused_function,merge_msg_Signed/3}). +merge_msg_Signed(#{} = PMsg, + #{payload := NFpayload} = NMsg, _) -> + S1 = #{payload => NFpayload}, + case {PMsg, NMsg} of + {_, #{signature := NFsignature}} -> + S1#{signature => NFsignature}; + {#{signature := PFsignature}, _} -> + S1#{signature => PFsignature}; + _ -> S1 + end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> + verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Signed' -> v_msg_Signed(Msg, [MsgName], TrUserData); + _ -> mk_type_error(not_a_known_message, Msg, []) + end. + + +-compile({nowarn_unused_function,v_msg_Signed/3}). +v_msg_Signed(#{payload := F1} = M, Path, TrUserData) -> + v_type_bytes(F1, [payload | Path], TrUserData), + case M of + #{signature := F2} -> + v_type_bytes(F2, [signature | Path], TrUserData); + _ -> ok + end, + lists:foreach(fun (payload) -> ok; + (signature) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Signed(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [payload] -- maps:keys(M), 'Signed'}, + M, Path); +v_msg_Signed(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Signed'}, X, Path). + +-compile({nowarn_unused_function,v_type_bytes/3}). +v_type_bytes(B, _Path, _TrUserData) when is_binary(B) -> + ok; +v_type_bytes(B, _Path, _TrUserData) when is_list(B) -> + ok; +v_type_bytes(X, Path, _TrUserData) -> + mk_type_error(bad_binary_value, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> + Path2 = prettify_path(Path), + erlang:error({gpb_type_error, + {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> + list_to_atom(string:join(lists:map(fun atom_to_list/1, + lists:reverse(PathR)), + ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> + [{{msg, 'Signed'}, + [#{name => payload, fnum => 1, rnum => 2, type => bytes, + occurrence => required, opts => []}, + #{name => signature, fnum => 2, rnum => 3, + type => bytes, occurrence => optional, opts => []}]}]. + + +get_msg_names() -> ['Signed']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Signed']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> + case find_msg_def(MsgName) of + Fs when is_list(Fs) -> Fs; + error -> erlang:error({no_such_msg, MsgName}) + end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> + erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Signed') -> + [#{name => payload, fnum => 1, rnum => 2, type => bytes, + occurrence => required, opts => []}, + #{name => signature, fnum => 2, rnum => 3, + type => bytes, occurrence => optional, opts => []}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> + erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> + erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> + erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> + "4.3.1". + +gpb_version_as_list() -> + [4,3,1]. diff --git a/src/r3_hex_pb_versions.erl b/src/r3_hex_pb_versions.erl new file mode 100644 index 0000000..66407dd --- /dev/null +++ b/src/r3_hex_pb_versions.erl @@ -0,0 +1,958 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%% -*- coding: utf-8 -*- +%% Automatically generated, do not edit +%% Generated by gpb_compile version 4.3.1 +-module(r3_hex_pb_versions). + +-export([encode_msg/2, encode_msg/3]). +-export([decode_msg/2, decode_msg/3]). +-export([merge_msgs/3, merge_msgs/4]). +-export([verify_msg/2, verify_msg/3]). +-export([get_msg_defs/0]). +-export([get_msg_names/0]). +-export([get_group_names/0]). +-export([get_msg_or_group_names/0]). +-export([get_enum_names/0]). +-export([find_msg_def/1, fetch_msg_def/1]). +-export([find_enum_def/1, fetch_enum_def/1]). +-export([enum_symbol_by_value/2, enum_value_by_symbol/2]). +-export([get_service_names/0]). +-export([get_service_def/1]). +-export([get_rpc_names/1]). +-export([find_rpc_def/2, fetch_rpc_def/2]). +-export([get_package_name/0]). +-export([gpb_version_as_string/0, gpb_version_as_list/0]). + + +%% enumerated types + +-export_type([]). + +%% message types +-type 'Versions'() :: + #{packages => ['Package'()], % = 1 + repository => iodata() % = 2 + }. + +-type 'Package'() :: + #{name => iodata(), % = 1 + versions => [iodata()], % = 2 + retired => [integer()] % = 3, 32 bits + }. + +-export_type(['Versions'/0, 'Package'/0]). + +-spec encode_msg('Versions'() | 'Package'(), atom()) -> binary(). +encode_msg(Msg, MsgName) when is_atom(MsgName) -> + encode_msg(Msg, MsgName, []). + +-spec encode_msg('Versions'() | 'Package'(), atom(), list()) -> binary(). +encode_msg(Msg, MsgName, Opts) -> + verify_msg(Msg, MsgName, Opts), + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Versions' -> + e_msg_Versions(id(Msg, TrUserData), TrUserData); + 'Package' -> + e_msg_Package(id(Msg, TrUserData), TrUserData) + end. + + +e_msg_Versions(Msg, TrUserData) -> + e_msg_Versions(Msg, <<>>, TrUserData). + + +e_msg_Versions(#{repository := F2} = M, Bin, + TrUserData) -> + B1 = case M of + #{packages := F1} -> + TrF1 = id(F1, TrUserData), + if TrF1 == [] -> Bin; + true -> e_field_Versions_packages(TrF1, Bin, TrUserData) + end; + _ -> Bin + end, + begin + TrF2 = id(F2, TrUserData), + e_type_string(TrF2, <<B1/binary, 18>>, TrUserData) + end. + +e_msg_Package(Msg, TrUserData) -> + e_msg_Package(Msg, <<>>, TrUserData). + + +e_msg_Package(#{name := F1} = M, Bin, TrUserData) -> + B1 = begin + TrF1 = id(F1, TrUserData), + e_type_string(TrF1, <<Bin/binary, 10>>, TrUserData) + end, + B2 = case M of + #{versions := F2} -> + TrF2 = id(F2, TrUserData), + if TrF2 == [] -> B1; + true -> e_field_Package_versions(TrF2, B1, TrUserData) + end; + _ -> B1 + end, + case M of + #{retired := F3} -> + TrF3 = id(F3, TrUserData), + if TrF3 == [] -> B2; + true -> e_field_Package_retired(TrF3, B2, TrUserData) + end; + _ -> B2 + end. + +e_mfield_Versions_packages(Msg, Bin, TrUserData) -> + SubBin = e_msg_Package(Msg, <<>>, TrUserData), + Bin2 = e_varint(byte_size(SubBin), Bin), + <<Bin2/binary, SubBin/binary>>. + +e_field_Versions_packages([Elem | Rest], Bin, + TrUserData) -> + Bin2 = <<Bin/binary, 10>>, + Bin3 = e_mfield_Versions_packages(id(Elem, TrUserData), + Bin2, TrUserData), + e_field_Versions_packages(Rest, Bin3, TrUserData); +e_field_Versions_packages([], Bin, _TrUserData) -> Bin. + +e_field_Package_versions([Elem | Rest], Bin, + TrUserData) -> + Bin2 = <<Bin/binary, 18>>, + Bin3 = e_type_string(id(Elem, TrUserData), Bin2, + TrUserData), + e_field_Package_versions(Rest, Bin3, TrUserData); +e_field_Package_versions([], Bin, _TrUserData) -> Bin. + +e_field_Package_retired(Elems, Bin, TrUserData) + when Elems =/= [] -> + SubBin = e_pfield_Package_retired(Elems, <<>>, + TrUserData), + Bin2 = <<Bin/binary, 26>>, + Bin3 = e_varint(byte_size(SubBin), Bin2), + <<Bin3/binary, SubBin/binary>>; +e_field_Package_retired([], Bin, _TrUserData) -> Bin. + +e_pfield_Package_retired([Value | Rest], Bin, + TrUserData) -> + Bin2 = e_type_int32(id(Value, TrUserData), Bin, + TrUserData), + e_pfield_Package_retired(Rest, Bin2, TrUserData); +e_pfield_Package_retired([], Bin, _TrUserData) -> Bin. + +-compile({nowarn_unused_function,e_type_sint/3}). +e_type_sint(Value, Bin, _TrUserData) when Value >= 0 -> + e_varint(Value * 2, Bin); +e_type_sint(Value, Bin, _TrUserData) -> + e_varint(Value * -2 - 1, Bin). + +-compile({nowarn_unused_function,e_type_int32/3}). +e_type_int32(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int32(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_int64/3}). +e_type_int64(Value, Bin, _TrUserData) + when 0 =< Value, Value =< 127 -> + <<Bin/binary, Value>>; +e_type_int64(Value, Bin, _TrUserData) -> + <<N:64/unsigned-native>> = <<Value:64/signed-native>>, + e_varint(N, Bin). + +-compile({nowarn_unused_function,e_type_bool/3}). +e_type_bool(true, Bin, _TrUserData) -> + <<Bin/binary, 1>>; +e_type_bool(false, Bin, _TrUserData) -> + <<Bin/binary, 0>>; +e_type_bool(1, Bin, _TrUserData) -> <<Bin/binary, 1>>; +e_type_bool(0, Bin, _TrUserData) -> <<Bin/binary, 0>>. + +-compile({nowarn_unused_function,e_type_string/3}). +e_type_string(S, Bin, _TrUserData) -> + Utf8 = unicode:characters_to_binary(S), + Bin2 = e_varint(byte_size(Utf8), Bin), + <<Bin2/binary, Utf8/binary>>. + +-compile({nowarn_unused_function,e_type_bytes/3}). +e_type_bytes(Bytes, Bin, _TrUserData) + when is_binary(Bytes) -> + Bin2 = e_varint(byte_size(Bytes), Bin), + <<Bin2/binary, Bytes/binary>>; +e_type_bytes(Bytes, Bin, _TrUserData) + when is_list(Bytes) -> + BytesBin = iolist_to_binary(Bytes), + Bin2 = e_varint(byte_size(BytesBin), Bin), + <<Bin2/binary, BytesBin/binary>>. + +-compile({nowarn_unused_function,e_type_fixed32/3}). +e_type_fixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little>>. + +-compile({nowarn_unused_function,e_type_sfixed32/3}). +e_type_sfixed32(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:32/little-signed>>. + +-compile({nowarn_unused_function,e_type_fixed64/3}). +e_type_fixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little>>. + +-compile({nowarn_unused_function,e_type_sfixed64/3}). +e_type_sfixed64(Value, Bin, _TrUserData) -> + <<Bin/binary, Value:64/little-signed>>. + +-compile({nowarn_unused_function,e_type_float/3}). +e_type_float(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:32/little-float>>; +e_type_float(infinity, Bin, _) -> + <<Bin/binary, 0:16, 128, 127>>; +e_type_float('-infinity', Bin, _) -> + <<Bin/binary, 0:16, 128, 255>>; +e_type_float(nan, Bin, _) -> + <<Bin/binary, 0:16, 192, 127>>. + +-compile({nowarn_unused_function,e_type_double/3}). +e_type_double(V, Bin, _) when is_number(V) -> + <<Bin/binary, V:64/little-float>>; +e_type_double(infinity, Bin, _) -> + <<Bin/binary, 0:48, 240, 127>>; +e_type_double('-infinity', Bin, _) -> + <<Bin/binary, 0:48, 240, 255>>; +e_type_double(nan, Bin, _) -> + <<Bin/binary, 0:48, 248, 127>>. + +-compile({nowarn_unused_function,e_varint/3}). +e_varint(N, Bin, _TrUserData) -> e_varint(N, Bin). + +-compile({nowarn_unused_function,e_varint/2}). +e_varint(N, Bin) when N =< 127 -> <<Bin/binary, N>>; +e_varint(N, Bin) -> + Bin2 = <<Bin/binary, (N band 127 bor 128)>>, + e_varint(N bsr 7, Bin2). + + +decode_msg(Bin, MsgName) when is_binary(Bin) -> + decode_msg(Bin, MsgName, []). + +decode_msg(Bin, MsgName, Opts) when is_binary(Bin) -> + TrUserData = proplists:get_value(user_data, Opts), + decode_msg_1_catch(Bin, MsgName, TrUserData). + +-ifdef('OTP_RELEASE'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +-ifdef('GPB_PATTERN_STACK'). +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason:StackTrace -> error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-else. +decode_msg_1_catch(Bin, MsgName, TrUserData) -> + try decode_msg_2_doit(MsgName, Bin, TrUserData) + catch Class:Reason -> + StackTrace = erlang:get_stacktrace(), + error({gpb_error,{decoding_failure, {Bin, MsgName, {Class, Reason, StackTrace}}}}) + end. +-endif. + +-endif. + +decode_msg_2_doit('Versions', Bin, TrUserData) -> + id(d_msg_Versions(Bin, TrUserData), TrUserData); +decode_msg_2_doit('Package', Bin, TrUserData) -> + id(d_msg_Package(Bin, TrUserData), TrUserData). + + + +d_msg_Versions(Bin, TrUserData) -> + dfp_read_field_def_Versions(Bin, 0, 0, + id([], TrUserData), id('$undef', TrUserData), + TrUserData). + +dfp_read_field_def_Versions(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Versions_packages(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Versions(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + d_field_Versions_repository(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +dfp_read_field_def_Versions(<<>>, 0, 0, R1, F@_2, + TrUserData) -> + S1 = #{repository => F@_2}, + if R1 == '$undef' -> S1; + true -> S1#{packages => lists_reverse(R1, TrUserData)} + end; +dfp_read_field_def_Versions(Other, Z1, Z2, F@_1, F@_2, + TrUserData) -> + dg_read_field_def_Versions(Other, Z1, Z2, F@_1, F@_2, + TrUserData). + +dg_read_field_def_Versions(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Versions(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +dg_read_field_def_Versions(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Versions_packages(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 18 -> + d_field_Versions_repository(Rest, 0, 0, F@_1, F@_2, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Versions(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 1 -> + skip_64_Versions(Rest, 0, 0, F@_1, F@_2, TrUserData); + 2 -> + skip_length_delimited_Versions(Rest, 0, 0, F@_1, F@_2, + TrUserData); + 3 -> + skip_group_Versions(Rest, Key bsr 3, 0, F@_1, F@_2, + TrUserData); + 5 -> + skip_32_Versions(Rest, 0, 0, F@_1, F@_2, TrUserData) + end + end; +dg_read_field_def_Versions(<<>>, 0, 0, R1, F@_2, + TrUserData) -> + S1 = #{repository => F@_2}, + if R1 == '$undef' -> S1; + true -> S1#{packages => lists_reverse(R1, TrUserData)} + end. + +d_field_Versions_packages(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Versions_packages(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +d_field_Versions_packages(<<0:1, X:7, Rest/binary>>, N, + Acc, Prev, F@_2, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bs:Len/binary, Rest2/binary>> = Rest, + {id(d_msg_Package(Bs, TrUserData), TrUserData), + Rest2} + end, + dfp_read_field_def_Versions(RestF, 0, 0, + cons(NewFValue, Prev, TrUserData), F@_2, + TrUserData). + +d_field_Versions_repository(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + d_field_Versions_repository(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, TrUserData); +d_field_Versions_repository(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, _, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Versions(RestF, 0, 0, F@_1, + NewFValue, TrUserData). + +skip_varint_Versions(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + skip_varint_Versions(Rest, Z1, Z2, F@_1, F@_2, + TrUserData); +skip_varint_Versions(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, TrUserData) -> + dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_length_delimited_Versions(<<1:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) + when N < 57 -> + skip_length_delimited_Versions(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, TrUserData); +skip_length_delimited_Versions(<<0:1, X:7, + Rest/binary>>, + N, Acc, F@_1, F@_2, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Versions(Rest2, 0, 0, F@_1, F@_2, + TrUserData). + +skip_group_Versions(Bin, FNum, Z2, F@_1, F@_2, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Versions(Rest, 0, Z2, F@_1, F@_2, + TrUserData). + +skip_32_Versions(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, TrUserData) -> + dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +skip_64_Versions(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, TrUserData) -> + dfp_read_field_def_Versions(Rest, Z1, Z2, F@_1, F@_2, + TrUserData). + +d_msg_Package(Bin, TrUserData) -> + dfp_read_field_def_Package(Bin, 0, 0, + id('$undef', TrUserData), id([], TrUserData), + id([], TrUserData), TrUserData). + +dfp_read_field_def_Package(<<10, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_name(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<18, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_versions(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<26, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_pfield_Package_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<24, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + d_field_Package_retired(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +dfp_read_field_def_Package(<<>>, 0, 0, F@_1, R1, R2, + TrUserData) -> + #{name => F@_1, + versions => lists_reverse(R1, TrUserData), + retired => lists_reverse(R2, TrUserData)}; +dfp_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData) -> + dg_read_field_def_Package(Other, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +dg_read_field_def_Package(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 32 - 7 -> + dg_read_field_def_Package(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +dg_read_field_def_Package(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) -> + Key = X bsl N + Acc, + case Key of + 10 -> + d_field_Package_name(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 18 -> + d_field_Package_versions(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 26 -> + d_pfield_Package_retired(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 24 -> + d_field_Package_retired(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + _ -> + case Key band 7 of + 0 -> + skip_varint_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 1 -> + skip_64_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData); + 2 -> + skip_length_delimited_Package(Rest, 0, 0, F@_1, F@_2, + F@_3, TrUserData); + 3 -> + skip_group_Package(Rest, Key bsr 3, 0, F@_1, F@_2, F@_3, + TrUserData); + 5 -> + skip_32_Package(Rest, 0, 0, F@_1, F@_2, F@_3, + TrUserData) + end + end; +dg_read_field_def_Package(<<>>, 0, 0, F@_1, R1, R2, + TrUserData) -> + #{name => F@_1, + versions => lists_reverse(R1, TrUserData), + retired => lists_reverse(R2, TrUserData)}. + +d_field_Package_name(<<1:1, X:7, Rest/binary>>, N, Acc, + F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_name(Rest, N + 7, X bsl N + Acc, F@_1, + F@_2, F@_3, TrUserData); +d_field_Package_name(<<0:1, X:7, Rest/binary>>, N, Acc, + _, F@_2, F@_3, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, NewFValue, F@_2, + F@_3, TrUserData). + +d_field_Package_versions(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_versions(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +d_field_Package_versions(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, Prev, F@_3, TrUserData) -> + {NewFValue, RestF} = begin + Len = X bsl N + Acc, + <<Bytes:Len/binary, Rest2/binary>> = Rest, + {id(binary:copy(Bytes), TrUserData), Rest2} + end, + dfp_read_field_def_Package(RestF, 0, 0, F@_1, + cons(NewFValue, Prev, TrUserData), F@_3, + TrUserData). + +d_field_Package_retired(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_field_Package_retired(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +d_field_Package_retired(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, Prev, TrUserData) -> + {NewFValue, RestF} = {begin + <<Res:32/signed-native>> = <<(X bsl N + + Acc):32/unsigned-native>>, + id(Res, TrUserData) + end, + Rest}, + dfp_read_field_def_Package(RestF, 0, 0, F@_1, F@_2, + cons(NewFValue, Prev, TrUserData), TrUserData). + +d_pfield_Package_retired(<<1:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + d_pfield_Package_retired(Rest, N + 7, X bsl N + Acc, + F@_1, F@_2, F@_3, TrUserData); +d_pfield_Package_retired(<<0:1, X:7, Rest/binary>>, N, + Acc, F@_1, F@_2, E, TrUserData) -> + Len = X bsl N + Acc, + <<PackedBytes:Len/binary, Rest2/binary>> = Rest, + NewSeq = d_packed_field_Package_retired(PackedBytes, 0, + 0, E, TrUserData), + dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, + NewSeq, TrUserData). + +d_packed_field_Package_retired(<<1:1, X:7, + Rest/binary>>, + N, Acc, AccSeq, TrUserData) + when N < 57 -> + d_packed_field_Package_retired(Rest, N + 7, + X bsl N + Acc, AccSeq, TrUserData); +d_packed_field_Package_retired(<<0:1, X:7, + Rest/binary>>, + N, Acc, AccSeq, TrUserData) -> + {NewFValue, RestF} = {begin + <<Res:32/signed-native>> = <<(X bsl N + + Acc):32/unsigned-native>>, + id(Res, TrUserData) + end, + Rest}, + d_packed_field_Package_retired(RestF, 0, 0, + [NewFValue | AccSeq], TrUserData); +d_packed_field_Package_retired(<<>>, 0, 0, AccSeq, _) -> + AccSeq. + +skip_varint_Package(<<1:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + skip_varint_Package(Rest, Z1, Z2, F@_1, F@_2, F@_3, + TrUserData); +skip_varint_Package(<<0:1, _:7, Rest/binary>>, Z1, Z2, + F@_1, F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_length_delimited_Package(<<1:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, TrUserData) + when N < 57 -> + skip_length_delimited_Package(Rest, N + 7, + X bsl N + Acc, F@_1, F@_2, F@_3, TrUserData); +skip_length_delimited_Package(<<0:1, X:7, Rest/binary>>, + N, Acc, F@_1, F@_2, F@_3, TrUserData) -> + Length = X bsl N + Acc, + <<_:Length/binary, Rest2/binary>> = Rest, + dfp_read_field_def_Package(Rest2, 0, 0, F@_1, F@_2, + F@_3, TrUserData). + +skip_group_Package(Bin, FNum, Z2, F@_1, F@_2, F@_3, + TrUserData) -> + {_, Rest} = read_group(Bin, FNum), + dfp_read_field_def_Package(Rest, 0, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_32_Package(<<_:32, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +skip_64_Package(<<_:64, Rest/binary>>, Z1, Z2, F@_1, + F@_2, F@_3, TrUserData) -> + dfp_read_field_def_Package(Rest, Z1, Z2, F@_1, F@_2, + F@_3, TrUserData). + +read_group(Bin, FieldNum) -> + {NumBytes, EndTagLen} = read_gr_b(Bin, 0, 0, 0, 0, FieldNum), + <<Group:NumBytes/binary, _:EndTagLen/binary, Rest/binary>> = Bin, + {Group, Rest}. + +%% Like skipping over fields, but record the total length, +%% Each field is <(FieldNum bsl 3) bor FieldType> ++ <FieldValue> +%% Record the length because varints may be non-optimally encoded. +%% +%% Groups can be nested, but assume the same FieldNum cannot be nested +%% because group field numbers are shared with the rest of the fields +%% numbers. Thus we can search just for an group-end with the same +%% field number. +%% +%% (The only time the same group field number could occur would +%% be in a nested sub message, but then it would be inside a +%% length-delimited entry, which we skip-read by length.) +read_gr_b(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, FieldNum) + when N < (32-7) -> + read_gr_b(Tl, N+7, X bsl N + Acc, NumBytes, TagLen+1, FieldNum); +read_gr_b(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, TagLen, + FieldNum) -> + Key = X bsl N + Acc, + TagLen1 = TagLen + 1, + case {Key bsr 3, Key band 7} of + {FieldNum, 4} -> % 4 = group_end + {NumBytes, TagLen1}; + {_, 0} -> % 0 = varint + read_gr_vi(Tl, 0, NumBytes + TagLen1, FieldNum); + {_, 1} -> % 1 = bits64 + <<_:64, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 8, 0, FieldNum); + {_, 2} -> % 2 = length_delimited + read_gr_ld(Tl, 0, 0, NumBytes + TagLen1, FieldNum); + {_, 3} -> % 3 = group_start + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 4} -> % 4 = group_end + read_gr_b(Tl, 0, 0, NumBytes + TagLen1, 0, FieldNum); + {_, 5} -> % 5 = bits32 + <<_:32, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes + TagLen1 + 4, 0, FieldNum) + end. + +read_gr_vi(<<1:1, _:7, Tl/binary>>, N, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_vi(Tl, N+7, NumBytes+1, FieldNum); +read_gr_vi(<<0:1, _:7, Tl/binary>>, _, NumBytes, FieldNum) -> + read_gr_b(Tl, 0, 0, NumBytes+1, 0, FieldNum). + +read_gr_ld(<<1:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) + when N < (64-7) -> + read_gr_ld(Tl, N+7, X bsl N + Acc, NumBytes+1, FieldNum); +read_gr_ld(<<0:1, X:7, Tl/binary>>, N, Acc, NumBytes, FieldNum) -> + Len = X bsl N + Acc, + NumBytes1 = NumBytes + 1, + <<_:Len/binary, Tl2/binary>> = Tl, + read_gr_b(Tl2, 0, 0, NumBytes1 + Len, 0, FieldNum). + +merge_msgs(Prev, New, MsgName) when is_atom(MsgName) -> + merge_msgs(Prev, New, MsgName, []). + +merge_msgs(Prev, New, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Versions' -> merge_msg_Versions(Prev, New, TrUserData); + 'Package' -> merge_msg_Package(Prev, New, TrUserData) + end. + +-compile({nowarn_unused_function,merge_msg_Versions/3}). +merge_msg_Versions(#{} = PMsg, + #{repository := NFrepository} = NMsg, TrUserData) -> + S1 = #{repository => NFrepository}, + case {PMsg, NMsg} of + {#{packages := PFpackages}, + #{packages := NFpackages}} -> + S1#{packages => + 'erlang_++'(PFpackages, NFpackages, TrUserData)}; + {_, #{packages := NFpackages}} -> + S1#{packages => NFpackages}; + {#{packages := PFpackages}, _} -> + S1#{packages => PFpackages}; + {_, _} -> S1 + end. + +-compile({nowarn_unused_function,merge_msg_Package/3}). +merge_msg_Package(#{} = PMsg, #{name := NFname} = NMsg, + TrUserData) -> + S1 = #{name => NFname}, + S2 = case {PMsg, NMsg} of + {#{versions := PFversions}, + #{versions := NFversions}} -> + S1#{versions => + 'erlang_++'(PFversions, NFversions, TrUserData)}; + {_, #{versions := NFversions}} -> + S1#{versions => NFversions}; + {#{versions := PFversions}, _} -> + S1#{versions => PFversions}; + {_, _} -> S1 + end, + case {PMsg, NMsg} of + {#{retired := PFretired}, #{retired := NFretired}} -> + S2#{retired => + 'erlang_++'(PFretired, NFretired, TrUserData)}; + {_, #{retired := NFretired}} -> + S2#{retired => NFretired}; + {#{retired := PFretired}, _} -> + S2#{retired => PFretired}; + {_, _} -> S2 + end. + + +verify_msg(Msg, MsgName) when is_atom(MsgName) -> + verify_msg(Msg, MsgName, []). + +verify_msg(Msg, MsgName, Opts) -> + TrUserData = proplists:get_value(user_data, Opts), + case MsgName of + 'Versions' -> + v_msg_Versions(Msg, [MsgName], TrUserData); + 'Package' -> v_msg_Package(Msg, [MsgName], TrUserData); + _ -> mk_type_error(not_a_known_message, Msg, []) + end. + + +-compile({nowarn_unused_function,v_msg_Versions/3}). +v_msg_Versions(#{repository := F2} = M, Path, + TrUserData) -> + case M of + #{packages := F1} -> + if is_list(F1) -> + _ = [v_msg_Package(Elem, [packages | Path], TrUserData) + || Elem <- F1], + ok; + true -> + mk_type_error({invalid_list_of, {msg, 'Package'}}, F1, + [packages | Path]) + end; + _ -> ok + end, + v_type_string(F2, [repository | Path], TrUserData), + lists:foreach(fun (packages) -> ok; + (repository) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Versions(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, + [repository] -- maps:keys(M), 'Versions'}, + M, Path); +v_msg_Versions(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Versions'}, X, Path). + +-compile({nowarn_unused_function,v_msg_Package/3}). +v_msg_Package(#{name := F1} = M, Path, TrUserData) -> + v_type_string(F1, [name | Path], TrUserData), + case M of + #{versions := F2} -> + if is_list(F2) -> + _ = [v_type_string(Elem, [versions | Path], TrUserData) + || Elem <- F2], + ok; + true -> + mk_type_error({invalid_list_of, string}, F2, + [versions | Path]) + end; + _ -> ok + end, + case M of + #{retired := F3} -> + if is_list(F3) -> + _ = [v_type_int32(Elem, [retired | Path], TrUserData) + || Elem <- F3], + ok; + true -> + mk_type_error({invalid_list_of, int32}, F3, + [retired | Path]) + end; + _ -> ok + end, + lists:foreach(fun (name) -> ok; + (versions) -> ok; + (retired) -> ok; + (OtherKey) -> + mk_type_error({extraneous_key, OtherKey}, M, Path) + end, + maps:keys(M)), + ok; +v_msg_Package(M, Path, _TrUserData) when is_map(M) -> + mk_type_error({missing_fields, [name] -- maps:keys(M), + 'Package'}, + M, Path); +v_msg_Package(X, Path, _TrUserData) -> + mk_type_error({expected_msg, 'Package'}, X, Path). + +-compile({nowarn_unused_function,v_type_int32/3}). +v_type_int32(N, _Path, _TrUserData) + when -2147483648 =< N, N =< 2147483647 -> + ok; +v_type_int32(N, Path, _TrUserData) when is_integer(N) -> + mk_type_error({value_out_of_range, int32, signed, 32}, + N, Path); +v_type_int32(X, Path, _TrUserData) -> + mk_type_error({bad_integer, int32, signed, 32}, X, + Path). + +-compile({nowarn_unused_function,v_type_string/3}). +v_type_string(S, Path, _TrUserData) + when is_list(S); is_binary(S) -> + try unicode:characters_to_binary(S) of + B when is_binary(B) -> ok; + {error, _, _} -> + mk_type_error(bad_unicode_string, S, Path) + catch + error:badarg -> + mk_type_error(bad_unicode_string, S, Path) + end; +v_type_string(X, Path, _TrUserData) -> + mk_type_error(bad_unicode_string, X, Path). + +-compile({nowarn_unused_function,mk_type_error/3}). +-spec mk_type_error(_, _, list()) -> no_return(). +mk_type_error(Error, ValueSeen, Path) -> + Path2 = prettify_path(Path), + erlang:error({gpb_type_error, + {Error, [{value, ValueSeen}, {path, Path2}]}}). + + +-compile({nowarn_unused_function,prettify_path/1}). +prettify_path([]) -> top_level; +prettify_path(PathR) -> + list_to_atom(string:join(lists:map(fun atom_to_list/1, + lists:reverse(PathR)), + ".")). + + +-compile({nowarn_unused_function,id/2}). +-compile({inline,id/2}). +id(X, _TrUserData) -> X. + +-compile({nowarn_unused_function,v_ok/3}). +-compile({inline,v_ok/3}). +v_ok(_Value, _Path, _TrUserData) -> ok. + +-compile({nowarn_unused_function,m_overwrite/3}). +-compile({inline,m_overwrite/3}). +m_overwrite(_Prev, New, _TrUserData) -> New. + +-compile({nowarn_unused_function,cons/3}). +-compile({inline,cons/3}). +cons(Elem, Acc, _TrUserData) -> [Elem | Acc]. + +-compile({nowarn_unused_function,lists_reverse/2}). +-compile({inline,lists_reverse/2}). +'lists_reverse'(L, _TrUserData) -> lists:reverse(L). +-compile({nowarn_unused_function,'erlang_++'/3}). +-compile({inline,'erlang_++'/3}). +'erlang_++'(A, B, _TrUserData) -> A ++ B. + +get_msg_defs() -> + [{{msg, 'Versions'}, + [#{name => packages, fnum => 1, rnum => 2, + type => {msg, 'Package'}, occurrence => repeated, + opts => []}, + #{name => repository, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}]}, + {{msg, 'Package'}, + [#{name => name, fnum => 1, rnum => 2, type => string, + occurrence => required, opts => []}, + #{name => versions, fnum => 2, rnum => 3, + type => string, occurrence => repeated, opts => []}, + #{name => retired, fnum => 3, rnum => 4, type => int32, + occurrence => repeated, opts => [packed]}]}]. + + +get_msg_names() -> ['Versions', 'Package']. + + +get_group_names() -> []. + + +get_msg_or_group_names() -> ['Versions', 'Package']. + + +get_enum_names() -> []. + + +fetch_msg_def(MsgName) -> + case find_msg_def(MsgName) of + Fs when is_list(Fs) -> Fs; + error -> erlang:error({no_such_msg, MsgName}) + end. + + +-spec fetch_enum_def(_) -> no_return(). +fetch_enum_def(EnumName) -> + erlang:error({no_such_enum, EnumName}). + + +find_msg_def('Versions') -> + [#{name => packages, fnum => 1, rnum => 2, + type => {msg, 'Package'}, occurrence => repeated, + opts => []}, + #{name => repository, fnum => 2, rnum => 3, + type => string, occurrence => required, opts => []}]; +find_msg_def('Package') -> + [#{name => name, fnum => 1, rnum => 2, type => string, + occurrence => required, opts => []}, + #{name => versions, fnum => 2, rnum => 3, + type => string, occurrence => repeated, opts => []}, + #{name => retired, fnum => 3, rnum => 4, type => int32, + occurrence => repeated, opts => [packed]}]; +find_msg_def(_) -> error. + + +find_enum_def(_) -> error. + + +-spec enum_symbol_by_value(_, _) -> no_return(). +enum_symbol_by_value(E, V) -> + erlang:error({no_enum_defs, E, V}). + + +-spec enum_value_by_symbol(_, _) -> no_return(). +enum_value_by_symbol(E, V) -> + erlang:error({no_enum_defs, E, V}). + + + +get_service_names() -> []. + + +get_service_def(_) -> error. + + +get_rpc_names(_) -> error. + + +find_rpc_def(_, _) -> error. + + + +-spec fetch_rpc_def(_, _) -> no_return(). +fetch_rpc_def(ServiceName, RpcName) -> + erlang:error({no_such_rpc, ServiceName, RpcName}). + + +get_package_name() -> undefined. + + + +gpb_version_as_string() -> + "4.3.1". + +gpb_version_as_list() -> + [4,3,1]. diff --git a/src/r3_hex_registry.erl b/src/r3_hex_registry.erl new file mode 100644 index 0000000..86f9176 --- /dev/null +++ b/src/r3_hex_registry.erl @@ -0,0 +1,133 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_registry). +-export([ + encode_names/1, + decode_names/2, + encode_versions/1, + decode_versions/2, + encode_package/1, + decode_package/3, + sign_protobuf/2, + decode_signed/1, + decode_and_verify_signed/2, + sign/2, + verify/3 +]). +-include_lib("public_key/include/public_key.hrl"). + +-type private_key() :: public_key:rsa_private_key() | binary(). +-type public_key() :: public_key:rsa_public_key() | binary(). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Encode Names message. +encode_names(Names) -> + r3_hex_pb_names:encode_msg(Names, 'Names'). + +%% @doc +%% Decode message created with encode_names/1. +decode_names(Payload, no_verify) -> + #{packages := Packages} = r3_hex_pb_names:decode_msg(Payload, 'Names'), + {ok, Packages}; + +decode_names(Payload, Repository) -> + case r3_hex_pb_names:decode_msg(Payload, 'Names') of + #{repository := Repository, packages := Packages} -> + {ok, Packages}; + _ -> + {error, unverified} + end. + +%% @doc +%% Encode Versions message. +encode_versions(Versions) -> + r3_hex_pb_versions:encode_msg(Versions, 'Versions'). + +%% @doc +%% Decode message created with encode_versions/1. +decode_versions(Payload, no_verify) -> + #{packages := Packages} = r3_hex_pb_versions:decode_msg(Payload, 'Versions'), + {ok, Packages}; + +decode_versions(Payload, Repository) -> + case r3_hex_pb_versions:decode_msg(Payload, 'Versions') of + #{repository := Repository, packages := Packages} -> + {ok, Packages}; + _ -> + {error, unverified} + end. + +%% @doc +%% Encode Package message. +encode_package(Package) -> + r3_hex_pb_package:encode_msg(Package, 'Package'). + +%% @doc +%% Decode message created with encode_package/1. +decode_package(Payload, no_verify, no_verify) -> + #{releases := Releases} = r3_hex_pb_package:decode_msg(Payload, 'Package'), + {ok, Releases}; + +decode_package(Payload, Repository, Package) -> + case r3_hex_pb_package:decode_msg(Payload, 'Package') of + #{repository := Repository, name := Package, releases := Releases} -> + {ok, Releases}; + _ -> + {error, unverified} + end. + +%% @doc +%% Encode Signed message. +sign_protobuf(Payload, PrivateKey) -> + Signature = sign(Payload, PrivateKey), + r3_hex_pb_signed:encode_msg(#{payload => Payload, signature => Signature}, 'Signed'). + +%% @doc +%% Decode message created with sign_protobuf/2 without verification. +decode_signed(Signed) -> + r3_hex_pb_signed:decode_msg(Signed, 'Signed'). + +%% @doc +%% Decode message created with sign_protobuf/2 and verify it against public key. +-spec decode_and_verify_signed(map(), public_key()) -> {ok, binary()} | {error, term()}. +decode_and_verify_signed(Signed, PublicKey) -> + #{payload := Payload, signature := Signature} = decode_signed(Signed), + case verify(Payload, Signature, PublicKey) of + true -> {ok, Payload}; + false -> {error, unverified}; + {error, Reason} -> {error, Reason} + end. + +%% @doc +%% Signs binary with given private key. +-spec sign(binary(), private_key()) -> binary(). +sign(Binary, PrivateKey) -> + {ok, RSAPrivateKey} = key(PrivateKey), + public_key:sign(Binary, sha512, RSAPrivateKey). + +%% @doc +%% Verifies binary against signature and a public key. +-spec verify(binary(), binary(), public_key()) -> boolean() | {error, term()}. +verify(Binary, Signature, PublicKey) -> + case key(PublicKey) of + {ok, RSAPublicKey} -> public_key:verify(Binary, sha512, Signature, RSAPublicKey); + {error, Reason} -> {error, Reason} + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +key(#'RSAPublicKey'{} = Key) -> + {ok, Key}; +key(#'RSAPrivateKey'{} = Key) -> + {ok, Key}; +key(Binary) when is_binary(Binary) -> + case public_key:pem_decode(Binary) of + [Entry | _] -> {ok, public_key:pem_entry_decode(Entry)}; + _ -> {error, bad_key} + end. diff --git a/src/r3_hex_repo.erl b/src/r3_hex_repo.erl new file mode 100644 index 0000000..dd8264f --- /dev/null +++ b/src/r3_hex_repo.erl @@ -0,0 +1,174 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_repo). +-export([ + get_names/1, + get_versions/1, + get_package/2, + get_tarball/3 +]). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Gets names resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_names(r3_hex_core:default_config()). +%% {ok, {200, ..., +%% [ +%% #{name => <<"package1">>}, +%% #{name => <<"package2">>}, +%% ]}} +%% ''' +%% @end +get_names(Config) when is_map(Config) -> + Verify = maps:get(repo_verify_origin, Config, true), + Decoder = fun(Data) -> + case Verify of + true -> r3_hex_registry:decode_names(Data, repo_name(Config)); + false -> r3_hex_registry:decode_names(Data, no_verify) + end + end, + get_protobuf(Config, <<"names">>, Decoder). + +%% @doc +%% Gets versions resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_versions(Config). +%% {ok, {200, ..., +%% [ +%% #{name => <<"package1">>, retired => [], +%% versions => [<<"1.0.0">>]}, +%% #{name => <<"package2">>, retired => [<<"0.5.0>>"], +%% versions => [<<"0.5.0">>, <<"1.0.0">>]}, +%% ]}} +%% ''' +%% @end +get_versions(Config) when is_map(Config) -> + Verify = maps:get(repo_verify_origin, Config, true), + Decoder = fun(Data) -> + case Verify of + true -> r3_hex_registry:decode_versions(Data, repo_name(Config)); + false -> r3_hex_registry:decode_versions(Data, no_verify) + end + end, + get_protobuf(Config, <<"versions">>, Decoder). + +%% @doc +%% Gets package resource from the repository. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_repo:get_package(r3_hex_core:default_config(), <<"package1">>). +%% {ok, {200, ..., +%% { +%% #{checksum => ..., version => <<"0.5.0">>, dependencies => []}, +%% #{checksum => ..., version => <<"1.0.0">>, dependencies => [ +%% #{package => <<"package2">>, optional => true, requirement => <<"~> 0.1">>} +%% ]}, +%% ]}} +%% ''' +%% @end +get_package(Config, Name) when is_binary(Name) and is_map(Config) -> + Verify = maps:get(repo_verify_origin, Config, true), + Decoder = fun(Data) -> + case Verify of + true -> r3_hex_registry:decode_package(Data, repo_name(Config), Name); + false -> r3_hex_registry:decode_package(Data, no_verify, no_verify) + end + end, + get_protobuf(Config, <<"packages/", Name/binary>>, Decoder). + +%% @doc +%% Gets tarball from the repository. +%% +%% Examples: +%% +%% ``` +%% > {ok, {200, _, Tarball}} = r3_hex_repo:get_tarball(<<"package1">>, <<"1.0.0">>, r3_hex_core:default_config()), +%% > {ok, #{metadata := Metadata}} = r3_hex_tarball:unpack(Tarball, memory). +%% ''' +%% @end +get_tarball(Config, Name, Version) -> + ReqHeaders = make_headers(Config), + + case get(Config, tarball_url(Config, Name, Version), ReqHeaders) of + {ok, {200, RespHeaders, Tarball}} -> + {ok, {200, RespHeaders, Tarball}}; + + Other -> + Other + end. + +%%==================================================================== +%% Internal functions +%%==================================================================== + +get(Config, URI, Headers) -> + r3_hex_http:request(Config, get, URI, Headers, undefined). + +get_protobuf(Config, Path, Decoder) -> + PublicKey = maps:get(repo_public_key, Config), + ReqHeaders = make_headers(Config), + + case get(Config, build_url(Config, Path), ReqHeaders) of + {ok, {200, RespHeaders, Compressed}} -> + Signed = zlib:gunzip(Compressed), + case decode(Signed, PublicKey, Decoder, Config) of + {ok, Decoded} -> + {ok, {200, RespHeaders, Decoded}}; + + {error, _} = Error -> + Error + end; + + Other -> + Other + end. + +decode(Signed, PublicKey, Decoder, Config) -> + Verify = maps:get(repo_verify, Config, true), + + case Verify of + true -> + case r3_hex_registry:decode_and_verify_signed(Signed, PublicKey) of + {ok, Payload} -> + Decoder(Payload); + Other -> + Other + end; + false -> + #{payload := Payload} = r3_hex_registry:decode_signed(Signed), + Decoder(Payload) + end. + +repo_name(#{repo_organization := Name}) when is_binary(Name) -> Name; +repo_name(#{repo_name := Name}) when is_binary(Name) -> Name. + +tarball_url(Config, Name, Version) -> + Filename = tarball_filename(Name, Version), + build_url(Config, <<"tarballs/", Filename/binary>>). + +build_url(#{repo_url := URI, repo_organization := Org}, Path) when is_binary(Org) -> + <<URI/binary, "/repos/", Org/binary, "/", Path/binary>>; +build_url(#{repo_url := URI, repo_organization := undefined}, Path) -> + <<URI/binary, "/", Path/binary>>. + +tarball_filename(Name, Version) -> + <<Name/binary, "-", Version/binary, ".tar">>. + +make_headers(Config) -> + maps:fold(fun set_header/3, #{}, Config). + +set_header(http_etag, ETag, Headers) when is_binary(ETag) -> maps:put(<<"if-none-match">>, ETag, Headers); +set_header(repo_key, Token, Headers) when is_binary(Token) -> maps:put(<<"authorization">>, Token, Headers); +set_header(_, _, Headers) -> Headers. diff --git a/src/r3_hex_tarball.erl b/src/r3_hex_tarball.erl new file mode 100644 index 0000000..dd2c77a --- /dev/null +++ b/src/r3_hex_tarball.erl @@ -0,0 +1,507 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +-module(r3_hex_tarball). +-export([create/2, create_docs/1, unpack/2, format_checksum/1, format_error/1]). +-ifdef(TEST). +-export([do_decode_metadata/1, gzip/1, normalize_requirements/1]). +-endif. +-define(VERSION, <<"3">>). +-define(TARBALL_MAX_SIZE, 8 * 1024 * 1024). +-define(TARBALL_MAX_UNCOMPRESSED_SIZE, 64 * 1024 * 1024). +-define(BUILD_TOOL_FILES, [ + {<<"mix.exs">>, <<"mix">>}, + {<<"rebar.config">>, <<"rebar3">>}, + {<<"rebar">>, <<"rebar3">>}, + {<<"Makefile">>, <<"make">>}, + {<<"Makefile.win">>, <<"make">>} +]). +-include_lib("kernel/include/file.hrl"). + +-type checksum() :: binary(). +-type contents() :: #{filename() => binary()}. +-type filename() :: string(). +-type files() :: [filename() | {filename(), filename()}] | contents(). +-type metadata() :: map(). +-type tarball() :: binary(). + +%%==================================================================== +%% API functions +%%==================================================================== + +%% @doc +%% Creates a package tarball. +%% +%% Examples: +%% +%% ``` +%% > Metadata = #{<<"name">> => <<"foo">>, <<"version">> => <<"1.0.0">>}, +%% > Files = [{"src/foo.erl", <<"-module(foo).">>}], +%% > {ok, {Tarball, Checksum}} = r3_hex_tarball:create(Metadata, Files). +%% > Tarball. +%% <<86,69,...>> +%% > Checksum. +%% <<40,32,...>> +%% ''' +%% @end +-spec create(metadata(), files()) -> {ok, {tarball(), checksum()}}. +create(Metadata, Files) -> + MetadataBinary = encode_metadata(Metadata), + ContentsTarball = create_memory_tarball(Files), + ContentsTarballCompressed = gzip(ContentsTarball), + Checksum = checksum(?VERSION, MetadataBinary, ContentsTarballCompressed), + ChecksumBase16 = encode_base16(Checksum), + + OuterFiles = [ + {"VERSION", ?VERSION}, + {"CHECKSUM", ChecksumBase16}, + {"metadata.config", MetadataBinary}, + {"contents.tar.gz", ContentsTarballCompressed} + ], + + Tarball = create_memory_tarball(OuterFiles), + + UncompressedSize = byte_size(ContentsTarball), + + case(byte_size(Tarball) > ?TARBALL_MAX_SIZE) or (UncompressedSize > ?TARBALL_MAX_UNCOMPRESSED_SIZE) of + true -> + {error, {tarball, too_big}}; + + false -> + {ok, {Tarball, Checksum}} + end. + +%% @doc +%% Creates a docs tarball. +%% +%% Examples: +%% +%% ``` +%% > Files = [{"doc/index.html", <<"Docs">>}], +%% > {ok, {Tarball, Checksum}} = r3_hex_tarball:create_docs(Files). +%% > Tarball. +%% %%=> <<86,69,...>> +%% > Checksum. +%% %%=> <<40,32,...>> +%% ''' +%% @end +-spec create_docs(files()) -> {ok, {tarball(), checksum()}}. +create_docs(Files) -> + UncompressedTarball = create_memory_tarball(Files), + UncompressedSize = byte_size(UncompressedTarball), + Tarball = gzip(UncompressedTarball), + Checksum = checksum(Tarball), + Size = byte_size(Tarball), + + case(Size > ?TARBALL_MAX_SIZE) or (UncompressedSize > ?TARBALL_MAX_UNCOMPRESSED_SIZE) of + true -> + {error, {tarball, too_big}}; + + false -> + {ok, {Tarball, Checksum}} + end. + +%% @doc +%% Unpacks a package tarball. +%% +%% Examples: +%% +%% ``` +%% > r3_hex_tarball:unpack(Tarball, memory). +%% {ok,#{checksum => <<...>>, +%% contents => [{"src/foo.erl",<<"-module(foo).">>}], +%% metadata => #{<<"name">> => <<"foo">>, ...}}} +%% +%% > r3_hex_tarball:unpack(Tarball, "path/to/unpack"). +%% {ok,#{checksum => <<...>>, +%% metadata => #{<<"name">> => <<"foo">>, ...}}} +%% ''' +-spec unpack(tarball(), memory) -> + {ok, #{checksum => checksum(), metadata => metadata(), contents => contents()}} | + {error, term()}; + (tarball(), filename()) -> + {ok, #{checksum => checksum(), metadata => metadata()}} | + {error, term()}. +unpack(Tarball, _) when byte_size(Tarball) > ?TARBALL_MAX_SIZE -> + {error, {tarball, too_big}}; + +unpack(Tarball, Output) -> + case r3_hex_erl_tar:extract({binary, Tarball}, [memory]) of + {ok, []} -> + {error, {tarball, empty}}; + + {ok, FileList} -> + do_unpack(maps:from_list(FileList), Output); + + {error, Reason} -> + {error, {tarball, Reason}} + end. + +%% @doc +%% Returns base16-encoded representation of checksum. +-spec format_checksum(checksum()) -> binary(). +format_checksum(Checksum) -> + encode_base16(Checksum). + +%% @doc +%% Converts an error reason term to a human-readable error message string. +-spec format_error(term()) -> string(). +format_error({tarball, empty}) -> "empty tarball"; +format_error({tarball, too_big}) -> "tarball is too big"; +format_error({tarball, {missing_files, Files}}) -> io_lib:format("missing files: ~p", [Files]); +format_error({tarball, {invalid_files, Files}}) -> io_lib:format("invalid files: ~p", [Files]); +format_error({tarball, {bad_version, Vsn}}) -> io_lib:format("unsupported version: ~p", [Vsn]); +format_error({tarball, invalid_checksum}) -> "invalid tarball checksum"; +format_error({tarball, Reason}) -> "tarball error, " ++ r3_hex_erl_tar:format_error(Reason); +format_error({inner_tarball, Reason}) -> "inner tarball error, " ++ r3_hex_erl_tar:format_error(Reason); +format_error({metadata, invalid_terms}) -> "error reading package metadata: invalid terms"; +format_error({metadata, not_key_value}) -> "error reading package metadata: not in key-value format"; +format_error({metadata, Reason}) -> "error reading package metadata" ++ r3_safe_erl_term:format_error(Reason); + +format_error({checksum_mismatch, ExpectedChecksum, ActualChecksum}) -> + io_lib:format( + "tarball checksum mismatch~n~n" ++ + "Expected (base16-encoded): ~s~n" ++ + "Actual (base16-encoded): ~s", + [encode_base16(ExpectedChecksum), encode_base16(ActualChecksum)]). + +%%==================================================================== +%% Internal functions +%%==================================================================== + +checksum(Version, MetadataBinary, ContentsBinary) -> + Blob = <<Version/binary, MetadataBinary/binary, ContentsBinary/binary>>, + crypto:hash(sha256, Blob). + +checksum(ContentsBinary) -> + Blob = <<ContentsBinary/binary>>, + crypto:hash(sha256, Blob). + +encode_metadata(Meta) -> + Data = lists:map( + fun(MetaPair) -> + String = io_lib_pretty:print(binarify(MetaPair), [{encoding, utf8}]), + unicode:characters_to_binary([String, ".\n"]) + end, maps:to_list(Meta)), + iolist_to_binary(Data). + +do_unpack(Files, Output) -> + State = #{ + checksum => undefined, + contents => undefined, + files => Files, + metadata => undefined, + output => Output + }, + State1 = check_files(State), + State2 = check_version(State1), + State3 = check_checksum(State2), + State4 = decode_metadata(State3), + finish_unpack(State4). + +finish_unpack({error, _} = Error) -> + Error; +finish_unpack(#{metadata := Metadata, files := Files, output := Output}) -> + _Version = maps:get("VERSION", Files), + Checksum = decode_base16(maps:get("CHECKSUM", Files)), + ContentsBinary = maps:get("contents.tar.gz", Files), + case unpack_tarball(ContentsBinary, Output) of + ok -> + copy_metadata_config(Output, maps:get("metadata.config", Files)), + {ok, #{checksum => Checksum, metadata => Metadata}}; + + {ok, Contents} -> + {ok, #{checksum => Checksum, metadata => Metadata, contents => Contents}}; + + {error, Reason} -> + {error, {inner_tarball, Reason}} + end. + +copy_metadata_config(Output, MetadataBinary) -> + ok = file:write_file(filename:join(Output, "hex_metadata.config"), MetadataBinary). + +check_files(#{files := Files} = State) -> + RequiredFiles = ["VERSION", "CHECKSUM", "metadata.config", "contents.tar.gz"], + case diff_keys(Files, RequiredFiles, []) of + ok -> + State; + + {error, {missing_keys, Keys}} -> + {error, {tarball, {missing_files, Keys}}}; + + {error, {unknown_keys, Keys}} -> + {error, {tarball, {invalid_files, Keys}}} + end. + +check_version({error, _} = Error) -> + Error; +check_version(#{files := Files} = State) -> + case maps:get("VERSION", Files) of + <<"3">> -> + State; + + Version -> + {error, {tarball, {bad_version, Version}}} + end. + +check_checksum({error, _} = Error) -> + Error; +check_checksum(#{files := Files} = State) -> + ChecksumBase16 = maps:get("CHECKSUM", Files), + ExpectedChecksum = decode_base16(ChecksumBase16), + + Version = maps:get("VERSION", Files), + MetadataBinary = maps:get("metadata.config", Files), + ContentsBinary = maps:get("contents.tar.gz", Files), + ActualChecksum = checksum(Version, MetadataBinary, ContentsBinary), + + if + byte_size(ExpectedChecksum) /= 32 -> + {error, {tarball, invalid_checksum}}; + + ExpectedChecksum == ActualChecksum -> + maps:put(checksum, ExpectedChecksum, State); + + true -> + {error, {tarball, {checksum_mismatch, ExpectedChecksum, ActualChecksum}}} + end. + +decode_metadata({error, _} = Error) -> + Error; +decode_metadata(#{files := #{"metadata.config" := Binary}} = State) when is_binary(Binary) -> + case do_decode_metadata(Binary) of + #{} = Metadata -> maps:put(metadata, normalize_metadata(Metadata), State); + Other -> Other + end. + +do_decode_metadata(Binary) when is_binary(Binary) -> + {ok, String} = characters_to_list(Binary), + + case r3_safe_erl_term:string(String) of + {ok, Tokens, _Line} -> + try + Terms = r3_safe_erl_term:terms(Tokens), + maps:from_list(Terms) + catch + error:function_clause -> + {error, {metadata, invalid_terms}}; + + error:badarg -> + {error, {metadata, not_key_value}} + end; + + {error, {_Line, r3_safe_erl_term, Reason}, _Line2} -> + {error, {metadata, Reason}} + end. + +characters_to_list(Binary) -> + case unicode:characters_to_list(Binary) of + List when is_list(List) -> + {ok, List}; + {error, _, _} -> + case unicode:characters_to_list(Binary, latin1) of + List when is_list(List) -> {ok, List}; + Other -> Other + end + end. + +normalize_metadata(Metadata1) -> + Metadata2 = maybe_update_with(<<"requirements">>, fun normalize_requirements/1, Metadata1), + Metadata3 = maybe_update_with(<<"links">>, fun try_into_map/1, Metadata2), + Metadata4 = maybe_update_with(<<"extra">>, fun try_into_map/1, Metadata3), + guess_build_tools(Metadata4). + +normalize_requirements(Requirements) -> + case is_list(Requirements) andalso (Requirements /= []) andalso is_list(hd(Requirements)) of + true -> + maps:from_list(lists:map(fun normalize_legacy_requirement/1, Requirements)); + + false -> + try_into_map(fun normalize_normal_requirement/1, Requirements) + end. + +normalize_normal_requirement({Name, Requirement}) -> + {Name, try_into_map(Requirement)}. + +normalize_legacy_requirement(Requirement) -> + Map = maps:from_list(Requirement), + Name = maps:get(<<"name">>, Map), + {Name, maps:without([<<"name">>], Map)}. + +guess_build_tools(#{<<"build_tools">> := BuildTools} = Metadata) when is_list(BuildTools) -> + Metadata; +guess_build_tools(#{<<"files">> := Filenames} = Metadata) -> + BaseFiles = [Filename || Filename <- Filenames, filename:dirname(binary_to_list(Filename)) == "."], + BuildTools = lists:usort([Tool || {Filename, Tool} <- ?BUILD_TOOL_FILES, lists:member(Filename, BaseFiles)]), + Metadata#{<<"build_tools">> => BuildTools}; +guess_build_tools(Metadata) -> + Metadata. + +%%==================================================================== +%% Tar Helpers +%%==================================================================== + +unpack_tarball(ContentsBinary, memory) -> + r3_hex_erl_tar:extract({binary, ContentsBinary}, [memory, compressed]); +unpack_tarball(ContentsBinary, Output) -> + case r3_hex_erl_tar:extract({binary, ContentsBinary}, [{cwd, Output}, compressed]) of + ok -> + [try_updating_mtime(filename:join(Output, Path)) || Path <- filelib:wildcard("**", Output)], + ok; + Other -> + Other + end. + +%% let it silently fail for bad symlinks +try_updating_mtime(Path) -> + Time = calendar:universal_time(), + _ = file:write_file_info(Path, #file_info{mtime=Time}, [{time, universal}]), + ok. + +create_memory_tarball(Files) -> + Path = tmp_path(), + {ok, Tar} = r3_hex_erl_tar:open(Path, [write]), + + try + add_files(Tar, Files) + after + ok = r3_hex_erl_tar:close(Tar) + end, + {ok, Tarball} = file:read_file(Path), + ok = file:delete(Path), + Tarball. + +tmp_path() -> + "tmp_" ++ binary_to_list(encode_base16(crypto:strong_rand_bytes(32))). + +add_files(Tar, Files) when is_list(Files) -> + lists:map(fun(File) -> add_file(Tar, File) end, Files). + +add_file(Tar, {Filename, Contents}) when is_list(Filename) and is_binary(Contents) -> + ok = r3_hex_erl_tar:add(Tar, Contents, Filename, tar_opts()); +add_file(Tar, Filename) when is_list(Filename) -> + add_file(Tar, {Filename, Filename}); +add_file(Tar, {Filename, AbsFilename}) when is_list(Filename), is_list(AbsFilename) -> + {ok, FileInfo} = file:read_link_info(AbsFilename, []), + + case FileInfo#file_info.type of + symlink -> + ok = r3_hex_erl_tar:add(Tar, {Filename, AbsFilename}, tar_opts()); + directory -> + case file:list_dir(AbsFilename) of + {ok, []} -> + r3_hex_erl_tar:add(Tar, {Filename, AbsFilename}, tar_opts()); + + {ok, _} -> + ok + end; + _ -> + Mode = FileInfo#file_info.mode, + {ok, Contents} = file:read_file(AbsFilename), + ok = r3_hex_erl_tar:add(Tar, Contents, Filename, Mode, tar_opts()) + end. + +tar_opts() -> + NixEpoch = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), + Y2kEpoch = calendar:datetime_to_gregorian_seconds({{2000, 1, 1}, {0, 0, 0}}), + Epoch = Y2kEpoch - NixEpoch, + [{atime, Epoch}, {mtime, Epoch}, {ctime, Epoch}, {uid, 0}, {gid, 0}]. + +%% Reproducible gzip by not setting mtime and OS +%% +%% From https://tools.ietf.org/html/rfc1952 +%% +%% +---+---+---+---+---+---+---+---+---+---+ +%% |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) +%% +---+---+---+---+---+---+---+---+---+---+ +%% +%% +=======================+ +%% |...compressed blocks...| (more-->) +%% +=======================+ +%% +%% +---+---+---+---+---+---+---+---+ +%% | CRC32 | ISIZE | +%% +---+---+---+---+---+---+---+---+ +gzip(Uncompressed) -> + Compressed = gzip_no_header(Uncompressed), + Header = <<31, 139, 8, 0, 0, 0, 0, 0, 0, 0>>, + Crc = erlang:crc32(Uncompressed), + Size = byte_size(Uncompressed), + Trailer = <<Crc:32/little, Size:32/little>>, + iolist_to_binary([Header, Compressed, Trailer]). + +gzip_no_header(Uncompressed) -> + Zstream = zlib:open(), + + try + zlib:deflateInit(Zstream, default, deflated, -15, 8, default), + Compressed = zlib:deflate(Zstream, Uncompressed, finish), + zlib:deflateEnd(Zstream), + iolist_to_binary(Compressed) + after + zlib:close(Zstream) + end. + +%%==================================================================== +%% Helpers +%%==================================================================== + +binarify(Binary) when is_binary(Binary) -> Binary; +binarify(Number) when is_number(Number) -> Number; +binarify(Atom) when Atom == undefined orelse is_boolean(Atom) -> Atom; +binarify(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8); +binarify(List) when is_list(List) -> + [binarify(E) || E <- List]; +binarify({Key, Value}) -> + {binarify(Key), binarify(Value)}; +binarify(Map) when is_map(Map) -> + List = maps:to_list(Map), + lists:map(fun({K, V}) -> binarify({K, V}) end, List). + +diff_keys(Map, RequiredKeys, OptionalKeys) -> + Keys = maps:keys(Map), + MissingKeys = RequiredKeys -- Keys, + UnknownKeys = Keys -- (RequiredKeys ++ OptionalKeys), + + case {MissingKeys, UnknownKeys} of + {[], []} -> + ok; + + {_, [_ | _]} -> + {error, {unknown_keys, UnknownKeys}}; + + _ -> + {error, {missing_keys, MissingKeys}} + end. + +maybe_update_with(Key, Fun, Map) -> + case maps:find(Key, Map) of + {ok, Value} -> maps:put(Key, Fun(Value), Map); + error -> Map + end. + +try_into_map(List) -> + try_into_map(fun(X) -> X end, List). + +try_into_map(Fun, Input) -> + case is_list(Input) andalso lists:all(fun(E) -> is_tuple(E) andalso (tuple_size(E) == 2) end, Input) of + true -> maps:from_list(lists:map(Fun, Input)); + false -> Input + end. + +encode_base16(Binary) -> + <<X:256/big-unsigned-integer>> = Binary, + String = string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X]))), + list_to_binary(String). + +%% Based on https://github.com/goj/base16/blob/master/src/base16.erl +%% (C) 2012, Erlang Solutions Ltd. + +decode_base16(Base16) -> + << <<(unhex(H) bsl 4 + unhex(L))>> || <<H,L>> <= Base16 >>. + +unhex(D) when $0 =< D andalso D =< $9 -> + D - $0; +unhex(D) when $a =< D andalso D =< $f -> + 10 + D - $a; +unhex(D) when $A =< D andalso D =< $F -> + 10 + D - $A. diff --git a/src/r3_safe_erl_term.erl b/src/r3_safe_erl_term.erl new file mode 100644 index 0000000..34c306a --- /dev/null +++ b/src/r3_safe_erl_term.erl @@ -0,0 +1,678 @@ +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 0). +%% The source of this file is part of leex distribution, as such it +%% has the same Copyright as the other files in the leex +%% distribution. The Copyright is defined in the accompanying file +%% COPYRIGHT. However, the resultant scanner generated by leex is the +%% property of the creator of the scanner and is not covered by that +%% Copyright. + +-module(r3_safe_erl_term). + +-export([string/1,string/2,token/2,token/3,tokens/2,tokens/3]). +-export([format_error/1]). + +%% User code. This is placed here to allow extra attributes. +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 26). + +-export([terms/1]). + +terms(Tokens) -> + terms(Tokens, []). + +terms([{dot, _} = H], Buffer) -> + [buffer_to_term([H|Buffer])]; +terms([{dot, _} = H|T], Buffer) -> + [buffer_to_term([H|Buffer])|terms(T, [])]; +terms([H|T], Buffer) -> + terms(T, [H|Buffer]). + +buffer_to_term(Buffer) -> + {ok, Term} = erl_parse:parse_term(lists:reverse(Buffer)), + Term. + +unquote(TokenChars, TokenLen) -> + lists:sublist(TokenChars, 2, TokenLen - 2). + +tokenize_atom(TokenChars, TokenLine) -> + try list_to_existing_atom(TokenChars) of + Atom -> {token, {atom, TokenLine, Atom}} + catch + error:badarg -> {error, "illegal atom " ++ TokenChars} + end. + +escape([$\\|Cs]) -> + do_escape(Cs); +escape([C|Cs]) -> + [C|escape(Cs)]; +escape([]) -> []. + +do_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|escape(S)]; +do_escape([$^,C|Cs]) -> + [C band 31|escape(Cs)]; +do_escape([C|Cs]) when C >= $\000, C =< $\s -> + escape(Cs); +do_escape([C|Cs]) -> + [escape_char(C)|escape(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $\s; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. + +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 14). + +format_error({illegal,S}) -> ["illegal characters ",io_lib:write_string(S)]; +format_error({user,S}) -> S. + +string(String) -> string(String, 1). + +string(String, Line) -> string(String, Line, String, []). + +%% string(InChars, Line, TokenChars, Tokens) -> +%% {ok,Tokens,Line} | {error,ErrorInfo,Line}. +%% Note the line number going into yystate, L0, is line of token +%% start while line number returned is line of token end. We want line +%% of token start. + +string([], L, [], Ts) -> % No partial tokens! + {ok,yyrev(Ts),L}; +string(Ics0, L0, Tcs, Ts) -> + case yystate(yystate(), Ics0, L0, 0, reject, 0) of + {A,Alen,Ics1,L1} -> % Accepting end state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts); + {A,Alen,Ics1,L1,_S1} -> % Accepting transistion state + string_cont(Ics1, L1, yyaction(A, Alen, Tcs, L0), Ts); + {reject,_Alen,Tlen,_Ics1,L1,_S1} -> % After a non-accepting state + {error,{L0,?MODULE,{illegal,yypre(Tcs, Tlen+1)}},L1}; + {A,Alen,Tlen,_Ics1,L1,_S1} -> + Tcs1 = yysuf(Tcs, Alen), + L2 = adjust_line(Tlen, Alen, Tcs1, L1), + string_cont(Tcs1, L2, yyaction(A, Alen, Tcs, L0), Ts) + end. + +%% string_cont(RestChars, Line, Token, Tokens) +%% Test for and remove the end token wrapper. Push back characters +%% are prepended to RestChars. + +-dialyzer({nowarn_function, string_cont/4}). + +string_cont(Rest, Line, {token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {token,T,Push}, Ts) -> + NewRest = Push ++ Rest, + string(NewRest, Line, NewRest, [T|Ts]); +string_cont(Rest, Line, {end_token,T}, Ts) -> + string(Rest, Line, Rest, [T|Ts]); +string_cont(Rest, Line, {end_token,T,Push}, Ts) -> + NewRest = Push ++ Rest, + string(NewRest, Line, NewRest, [T|Ts]); +string_cont(Rest, Line, skip_token, Ts) -> + string(Rest, Line, Rest, Ts); +string_cont(Rest, Line, {skip_token,Push}, Ts) -> + NewRest = Push ++ Rest, + string(NewRest, Line, NewRest, Ts); +string_cont(_Rest, Line, {error,S}, _Ts) -> + {error,{Line,?MODULE,{user,S}},Line}. + +%% token(Continuation, Chars) -> +%% token(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. The continuation is: +%% {token,State,CurrLine,TokenChars,TokenLen,TokenLine,AccAction,AccLen} + +token(Cont, Chars) -> token(Cont, Chars, 1). + +token([], Chars, Line) -> + token(yystate(), Chars, Line, Chars, 0, Line, reject, 0); +token({token,State,Line,Tcs,Tlen,Tline,Action,Alen}, Chars, _) -> + token(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Action, Alen). + +%% token(State, InChars, Line, TokenChars, TokenLen, TokenLine, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% The argument order is chosen to be more efficient. + +token(S0, Ics0, L0, Tcs, Tlen0, Tline, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + %% Accepting end state, we have a token. + {A1,Alen1,Ics1,L1} -> + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline)); + %% Accepting transition state, can take more chars. + {A1,Alen1,[],L1,S1} -> % Need more chars to check + {more,{token,S1,L1,Tcs,Alen1,Tline,A1,Alen1}}; + {A1,Alen1,Ics1,L1,_S1} -> % Take what we got + token_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline)); + %% After a non-accepting state, maybe reach accept state later. + {A1,Alen1,Tlen1,[],L1,S1} -> % Need more chars to check + {more,{token,S1,L1,Tcs,Tlen1,Tline,A1,Alen1}}; + {reject,_Alen1,Tlen1,eof,L1,_S1} -> % No token match + %% Check for partial token which is error. + Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE, + %% Skip eof tail in Tcs. + {illegal,yypre(Tcs, Tlen1)}},L1}; + true -> {eof,L1} + end, + {done,Ret,eof}; + {reject,_Alen1,Tlen1,Ics1,L1,_S1} -> % No token match + Error = {Tline,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, + {done,{error,Error,L1},Ics1}; + {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> % Use last accept match + Tcs1 = yysuf(Tcs, Alen1), + L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), + token_cont(Tcs1, L2, yyaction(A1, Alen1, Tcs, Tline)) + end. + +%% token_cont(RestChars, Line, Token) +%% If we have a token or error then return done, else if we have a +%% skip_token then continue. + +-dialyzer({nowarn_function, token_cont/3}). + +token_cont(Rest, Line, {token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {token,T,Push}) -> + NewRest = Push ++ Rest, + {done,{ok,T,Line},NewRest}; +token_cont(Rest, Line, {end_token,T}) -> + {done,{ok,T,Line},Rest}; +token_cont(Rest, Line, {end_token,T,Push}) -> + NewRest = Push ++ Rest, + {done,{ok,T,Line},NewRest}; +token_cont(Rest, Line, skip_token) -> + token(yystate(), Rest, Line, Rest, 0, Line, reject, 0); +token_cont(Rest, Line, {skip_token,Push}) -> + NewRest = Push ++ Rest, + token(yystate(), NewRest, Line, NewRest, 0, Line, reject, 0); +token_cont(Rest, Line, {error,S}) -> + {done,{error,{Line,?MODULE,{user,S}},Line},Rest}. + +%% tokens(Continuation, Chars, Line) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. +%% Must be careful when re-entering to append the latest characters to the +%% after characters in an accept. The continuation is: +%% {tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Tokens,AccAction,AccLen} +%% {skip_tokens,State,CurrLine,TokenChars,TokenLen,TokenLine,Error,AccAction,AccLen} + +tokens(Cont, Chars) -> tokens(Cont, Chars, 1). + +tokens([], Chars, Line) -> + tokens(yystate(), Chars, Line, Chars, 0, Line, [], reject, 0); +tokens({tokens,State,Line,Tcs,Tlen,Tline,Ts,Action,Alen}, Chars, _) -> + tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Ts, Action, Alen); +tokens({skip_tokens,State,Line,Tcs,Tlen,Tline,Error,Action,Alen}, Chars, _) -> + skip_tokens(State, Chars, Line, Tcs ++ Chars, Tlen, Tline, Error, Action, Alen). + +%% tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Ts, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + %% Accepting end state, we have a token. + {A1,Alen1,Ics1,L1} -> + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts); + %% Accepting transition state, can take more chars. + {A1,Alen1,[],L1,S1} -> % Need more chars to check + {more,{tokens,S1,L1,Tcs,Alen1,Tline,Ts,A1,Alen1}}; + {A1,Alen1,Ics1,L1,_S1} -> % Take what we got + tokens_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Ts); + %% After a non-accepting state, maybe reach accept state later. + {A1,Alen1,Tlen1,[],L1,S1} -> % Need more chars to check + {more,{tokens,S1,L1,Tcs,Tlen1,Tline,Ts,A1,Alen1}}; + {reject,_Alen1,Tlen1,eof,L1,_S1} -> % No token match + %% Check for partial token which is error, no need to skip here. + Ret = if Tlen1 > 0 -> {error,{Tline,?MODULE, + %% Skip eof tail in Tcs. + {illegal,yypre(Tcs, Tlen1)}},L1}; + Ts == [] -> {eof,L1}; + true -> {ok,yyrev(Ts),L1} + end, + {done,Ret,eof}; + {reject,_Alen1,Tlen1,_Ics1,L1,_S1} -> + %% Skip rest of tokens. + Error = {L1,?MODULE,{illegal,yypre(Tcs, Tlen1+1)}}, + skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error); + {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> + Token = yyaction(A1, Alen1, Tcs, Tline), + Tcs1 = yysuf(Tcs, Alen1), + L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), + tokens_cont(Tcs1, L2, Token, Ts) + end. + +%% tokens_cont(RestChars, Line, Token, Tokens) +%% If we have an end_token or error then return done, else if we have +%% a token then save it and continue, else if we have a skip_token +%% just continue. + +-dialyzer({nowarn_function, tokens_cont/4}). + +tokens_cont(Rest, Line, {token,T}, Ts) -> + tokens(yystate(), Rest, Line, Rest, 0, Line, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {token,T,Push}, Ts) -> + NewRest = Push ++ Rest, + tokens(yystate(), NewRest, Line, NewRest, 0, Line, [T|Ts], reject, 0); +tokens_cont(Rest, Line, {end_token,T}, Ts) -> + {done,{ok,yyrev(Ts, [T]),Line},Rest}; +tokens_cont(Rest, Line, {end_token,T,Push}, Ts) -> + NewRest = Push ++ Rest, + {done,{ok,yyrev(Ts, [T]),Line},NewRest}; +tokens_cont(Rest, Line, skip_token, Ts) -> + tokens(yystate(), Rest, Line, Rest, 0, Line, Ts, reject, 0); +tokens_cont(Rest, Line, {skip_token,Push}, Ts) -> + NewRest = Push ++ Rest, + tokens(yystate(), NewRest, Line, NewRest, 0, Line, Ts, reject, 0); +tokens_cont(Rest, Line, {error,S}, _Ts) -> + skip_tokens(Rest, Line, {Line,?MODULE,{user,S}}). + +%%skip_tokens(InChars, Line, Error) -> {done,{error,Error,Line},Ics}. +%% Skip tokens until an end token, junk everything and return the error. + +skip_tokens(Ics, Line, Error) -> + skip_tokens(yystate(), Ics, Line, Ics, 0, Line, Error, reject, 0). + +%% skip_tokens(State, InChars, Line, TokenChars, TokenLen, TokenLine, Tokens, +%% AcceptAction, AcceptLen) -> +%% {more,Continuation} | {done,ReturnVal,RestChars}. + +skip_tokens(S0, Ics0, L0, Tcs, Tlen0, Tline, Error, A0, Alen0) -> + case yystate(S0, Ics0, L0, Tlen0, A0, Alen0) of + {A1,Alen1,Ics1,L1} -> % Accepting end state + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error); + {A1,Alen1,[],L1,S1} -> % After an accepting state + {more,{skip_tokens,S1,L1,Tcs,Alen1,Tline,Error,A1,Alen1}}; + {A1,Alen1,Ics1,L1,_S1} -> + skip_cont(Ics1, L1, yyaction(A1, Alen1, Tcs, Tline), Error); + {A1,Alen1,Tlen1,[],L1,S1} -> % After a non-accepting state + {more,{skip_tokens,S1,L1,Tcs,Tlen1,Tline,Error,A1,Alen1}}; + {reject,_Alen1,_Tlen1,eof,L1,_S1} -> + {done,{error,Error,L1},eof}; + {reject,_Alen1,Tlen1,_Ics1,L1,_S1} -> + skip_tokens(yysuf(Tcs, Tlen1+1), L1, Error); + {A1,Alen1,Tlen1,_Ics1,L1,_S1} -> + Token = yyaction(A1, Alen1, Tcs, Tline), + Tcs1 = yysuf(Tcs, Alen1), + L2 = adjust_line(Tlen1, Alen1, Tcs1, L1), + skip_cont(Tcs1, L2, Token, Error) + end. + +%% skip_cont(RestChars, Line, Token, Error) +%% Skip tokens until we have an end_token or error then return done +%% with the original rror. + +-dialyzer({nowarn_function, skip_cont/4}). + +skip_cont(Rest, Line, {token,_T}, Error) -> + skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {token,_T,Push}, Error) -> + NewRest = Push ++ Rest, + skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {end_token,_T}, Error) -> + {done,{error,Error,Line},Rest}; +skip_cont(Rest, Line, {end_token,_T,Push}, Error) -> + NewRest = Push ++ Rest, + {done,{error,Error,Line},NewRest}; +skip_cont(Rest, Line, skip_token, Error) -> + skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {skip_token,Push}, Error) -> + NewRest = Push ++ Rest, + skip_tokens(yystate(), NewRest, Line, NewRest, 0, Line, Error, reject, 0); +skip_cont(Rest, Line, {error,_S}, Error) -> + skip_tokens(yystate(), Rest, Line, Rest, 0, Line, Error, reject, 0). + +-compile({nowarn_unused_function, [yyrev/1, yyrev/2, yypre/2, yysuf/2]}). + +yyrev(List) -> lists:reverse(List). +yyrev(List, Tail) -> lists:reverse(List, Tail). +yypre(List, N) -> lists:sublist(List, N). +yysuf(List, N) -> lists:nthtail(N, List). + +%% adjust_line(TokenLength, AcceptLength, Chars, Line) -> NewLine +%% Make sure that newlines in Chars are not counted twice. +%% Line has been updated with respect to newlines in the prefix of +%% Chars consisting of (TokenLength - AcceptLength) characters. + +-compile({nowarn_unused_function, adjust_line/4}). + +adjust_line(N, N, _Cs, L) -> L; +adjust_line(T, A, [$\n|Cs], L) -> + adjust_line(T-1, A, Cs, L-1); +adjust_line(T, A, [_|Cs], L) -> + adjust_line(T-1, A, Cs, L). + +%% yystate() -> InitialState. +%% yystate(State, InChars, Line, CurrTokLen, AcceptAction, AcceptLen) -> +%% {Action, AcceptLen, RestChars, Line} | +%% {Action, AcceptLen, RestChars, Line, State} | +%% {reject, AcceptLen, CurrTokLen, RestChars, Line, State} | +%% {Action, AcceptLen, CurrTokLen, RestChars, Line, State}. +%% Generated state transition functions. The non-accepting end state +%% return signal either an unrecognised character or end of current +%% input. + +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.erl", 360). +yystate() -> 13. + +yystate(20, [60|Ics], Line, Tlen, Action, Alen) -> + yystate(10, Ics, Line, Tlen+1, Action, Alen); +yystate(20, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,20}; +yystate(19, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [39|Ics], Line, Tlen, Action, Alen) -> + yystate(15, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(19, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,19}; +yystate(18, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [34|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(18, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,18}; +yystate(17, [37|Ics], Line, Tlen, _, _) -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, [10|Ics], Line, Tlen, _, _) -> + yystate(17, Ics, Line+1, Tlen+1, 8, Tlen); +yystate(17, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> + yystate(17, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 32 -> + yystate(17, Ics, Line, Tlen+1, 8, Tlen); +yystate(17, Ics, Line, Tlen, _, _) -> + {8,Tlen,Ics,Line,17}; +yystate(16, Ics, Line, Tlen, _, _) -> + {4,Tlen,Ics,Line}; +yystate(15, [92|Ics], Line, Tlen, _, _) -> + yystate(11, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [39|Ics], Line, Tlen, _, _) -> + yystate(7, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [10|Ics], Line, Tlen, _, _) -> + yystate(0, Ics, Line+1, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> + yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 38 -> + yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 40, C =< 91 -> + yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, [C|Ics], Line, Tlen, _, _) when C >= 93 -> + yystate(0, Ics, Line, Tlen+1, 1, Tlen); +yystate(15, Ics, Line, Tlen, _, _) -> + {1,Tlen,Ics,Line,15}; +yystate(14, [94|Ics], Line, Tlen, Action, Alen) -> + yystate(18, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [93|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [34|Ics], Line, Tlen, Action, Alen) -> + yystate(6, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, [C|Ics], Line, Tlen, Action, Alen) when C >= 95 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(14, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,14}; +yystate(13, [125|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [123|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [93|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [91|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [62|Ics], Line, Tlen, Action, Alen) -> + yystate(2, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [61|Ics], Line, Tlen, Action, Alen) -> + yystate(2, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [60|Ics], Line, Tlen, Action, Alen) -> + yystate(20, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [47|Ics], Line, Tlen, Action, Alen) -> + yystate(4, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [46|Ics], Line, Tlen, Action, Alen) -> + yystate(3, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [39|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [37|Ics], Line, Tlen, Action, Alen) -> + yystate(8, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [35|Ics], Line, Tlen, Action, Alen) -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [34|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(17, Ics, Line+1, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 32 -> + yystate(17, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 43, C =< 45 -> + yystate(16, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 48, C =< 57 -> + yystate(12, Ics, Line, Tlen+1, Action, Alen); +yystate(13, [C|Ics], Line, Tlen, Action, Alen) when C >= 97, C =< 122 -> + yystate(5, Ics, Line, Tlen+1, Action, Alen); +yystate(13, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,13}; +yystate(12, [C|Ics], Line, Tlen, _, _) when C >= 48, C =< 57 -> + yystate(12, Ics, Line, Tlen+1, 3, Tlen); +yystate(12, Ics, Line, Tlen, _, _) -> + {3,Tlen,Ics,Line,12}; +yystate(11, [94|Ics], Line, Tlen, Action, Alen) -> + yystate(19, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [93|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [39|Ics], Line, Tlen, Action, Alen) -> + yystate(15, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, [C|Ics], Line, Tlen, Action, Alen) when C >= 95 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(11, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,11}; +yystate(10, Ics, Line, Tlen, _, _) -> + {5,Tlen,Ics,Line}; +yystate(9, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(14, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [34|Ics], Line, Tlen, Action, Alen) -> + yystate(1, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(9, Ics, Line+1, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 33 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 35, C =< 91 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> + yystate(9, Ics, Line, Tlen+1, Action, Alen); +yystate(9, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,9}; +yystate(8, [37|Ics], Line, Tlen, _, _) -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [10|Ics], Line, Tlen, _, _) -> + yystate(17, Ics, Line+1, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 32 -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 33, C =< 36 -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, [C|Ics], Line, Tlen, _, _) when C >= 38 -> + yystate(8, Ics, Line, Tlen+1, 8, Tlen); +yystate(8, Ics, Line, Tlen, _, _) -> + {8,Tlen,Ics,Line,8}; +yystate(7, Ics, Line, Tlen, _, _) -> + {1,Tlen,Ics,Line}; +yystate(6, [92|Ics], Line, Tlen, _, _) -> + yystate(14, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [34|Ics], Line, Tlen, _, _) -> + yystate(1, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [10|Ics], Line, Tlen, _, _) -> + yystate(9, Ics, Line+1, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 0, C =< 9 -> + yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 11, C =< 33 -> + yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 35, C =< 91 -> + yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, [C|Ics], Line, Tlen, _, _) when C >= 93 -> + yystate(9, Ics, Line, Tlen+1, 2, Tlen); +yystate(6, Ics, Line, Tlen, _, _) -> + {2,Tlen,Ics,Line,6}; +yystate(5, [95|Ics], Line, Tlen, _, _) -> + yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [64|Ics], Line, Tlen, _, _) -> + yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 48, C =< 57 -> + yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 65, C =< 90 -> + yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, [C|Ics], Line, Tlen, _, _) when C >= 97, C =< 122 -> + yystate(5, Ics, Line, Tlen+1, 0, Tlen); +yystate(5, Ics, Line, Tlen, _, _) -> + {0,Tlen,Ics,Line,5}; +yystate(4, Ics, Line, Tlen, _, _) -> + {7,Tlen,Ics,Line}; +yystate(3, Ics, Line, Tlen, _, _) -> + {6,Tlen,Ics,Line}; +yystate(2, [62|Ics], Line, Tlen, Action, Alen) -> + yystate(10, Ics, Line, Tlen+1, Action, Alen); +yystate(2, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,2}; +yystate(1, Ics, Line, Tlen, _, _) -> + {2,Tlen,Ics,Line}; +yystate(0, [92|Ics], Line, Tlen, Action, Alen) -> + yystate(11, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [39|Ics], Line, Tlen, Action, Alen) -> + yystate(7, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [10|Ics], Line, Tlen, Action, Alen) -> + yystate(0, Ics, Line+1, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 0, C =< 9 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 11, C =< 38 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 40, C =< 91 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, [C|Ics], Line, Tlen, Action, Alen) when C >= 93 -> + yystate(0, Ics, Line, Tlen+1, Action, Alen); +yystate(0, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,0}; +yystate(S, Ics, Line, Tlen, Action, Alen) -> + {Action,Alen,Tlen,Ics,Line,S}. + +%% yyaction(Action, TokenLength, TokenChars, TokenLine) -> +%% {token,Token} | {end_token, Token} | skip_token | {error,String}. +%% Generated action function. + +yyaction(0, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_0(TokenChars, TokenLine); +yyaction(1, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_1(TokenChars, TokenLen, TokenLine); +yyaction(2, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_2(TokenChars, TokenLen, TokenLine); +yyaction(3, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_3(TokenChars, TokenLine); +yyaction(4, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_4(TokenChars, TokenLine); +yyaction(5, TokenLen, YYtcs, TokenLine) -> + TokenChars = yypre(YYtcs, TokenLen), + yyaction_5(TokenChars, TokenLine); +yyaction(6, _, _, TokenLine) -> + yyaction_6(TokenLine); +yyaction(7, _, _, TokenLine) -> + yyaction_7(TokenLine); +yyaction(8, _, _, _) -> + yyaction_8(); +yyaction(_, _, _, _) -> error. + +-compile({inline,yyaction_0/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 14). +yyaction_0(TokenChars, TokenLine) -> + tokenize_atom (TokenChars, TokenLine) . + +-compile({inline,yyaction_1/3}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 15). +yyaction_1(TokenChars, TokenLen, TokenLine) -> + tokenize_atom (escape (unquote (TokenChars, TokenLen)), TokenLine) . + +-compile({inline,yyaction_2/3}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 16). +yyaction_2(TokenChars, TokenLen, TokenLine) -> + { token, { string, TokenLine, escape (unquote (TokenChars, TokenLen)) } } . + +-compile({inline,yyaction_3/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 17). +yyaction_3(TokenChars, TokenLine) -> + { token, { integer, TokenLine, list_to_integer (TokenChars) } } . + +-compile({inline,yyaction_4/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 18). +yyaction_4(TokenChars, TokenLine) -> + { token, { list_to_atom (TokenChars), TokenLine } } . + +-compile({inline,yyaction_5/2}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 19). +yyaction_5(TokenChars, TokenLine) -> + { token, { list_to_atom (TokenChars), TokenLine } } . + +-compile({inline,yyaction_6/1}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 20). +yyaction_6(TokenLine) -> + { token, { dot, TokenLine } } . + +-compile({inline,yyaction_7/1}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 21). +yyaction_7(TokenLine) -> + { token, { '/', TokenLine } } . + +-compile({inline,yyaction_8/0}). +-file("/Users/starbelly/devel/erlang/rebar3/src/hex_core/r3_safe_erl_term.xrl", 22). +yyaction_8() -> + skip_token . + +-file("/usr/local/lib/erlang/lib/parsetools-2.1.8/include/leexinc.hrl", 313). diff --git a/src/r3_safe_erl_term.xrl b/src/r3_safe_erl_term.xrl new file mode 100644 index 0000000..742aa6b --- /dev/null +++ b/src/r3_safe_erl_term.xrl @@ -0,0 +1,79 @@ +%% Vendored from hex_core v0.5.0, do not edit manually + +%%% Author : Robert Virding +%%% Purpose : Token definitions for Erlang. + +Definitions. + +D = [0-9] +U = [A-Z] +L = [a-z] +A = ({U}|{L}|{D}|_|@) +WS = ([\000-\s]|%.*) + +Rules. + +{L}{A}* : tokenize_atom(TokenChars, TokenLine). +'(\\\^.|\\.|[^'])*' : tokenize_atom(escape(unquote(TokenChars, TokenLen)), TokenLine). +"(\\\^.|\\.|[^"])*" : {token, {string, TokenLine, escape(unquote(TokenChars, TokenLen))}}. +{D}+ : {token, {integer, TokenLine, list_to_integer(TokenChars)}}. +[\#\[\]}{,+-] : {token, {list_to_atom(TokenChars), TokenLine}}. +(<<|>>|=>) : {token, {list_to_atom(TokenChars), TokenLine}}. +\. : {token, {dot, TokenLine}}. +/ : {token, {'/', TokenLine}}. +{WS}+ : skip_token. + +Erlang code. + +-export([terms/1]). + +terms(Tokens) -> + terms(Tokens, []). + +terms([{dot, _} = H], Buffer) -> + [buffer_to_term([H|Buffer])]; +terms([{dot, _} = H|T], Buffer) -> + [buffer_to_term([H|Buffer])|terms(T, [])]; +terms([H|T], Buffer) -> + terms(T, [H|Buffer]). + +buffer_to_term(Buffer) -> + {ok, Term} = erl_parse:parse_term(lists:reverse(Buffer)), + Term. + +unquote(TokenChars, TokenLen) -> + lists:sublist(TokenChars, 2, TokenLen - 2). + +tokenize_atom(TokenChars, TokenLine) -> + try list_to_existing_atom(TokenChars) of + Atom -> {token, {atom, TokenLine, Atom}} + catch + error:badarg -> {error, "illegal atom " ++ TokenChars} + end. + +escape([$\\|Cs]) -> + do_escape(Cs); +escape([C|Cs]) -> + [C|escape(Cs)]; +escape([]) -> []. + +do_escape([O1,O2,O3|S]) when + O1 >= $0, O1 =< $7, O2 >= $0, O2 =< $7, O3 >= $0, O3 =< $7 -> + [(O1*8 + O2)*8 + O3 - 73*$0|escape(S)]; +do_escape([$^,C|Cs]) -> + [C band 31|escape(Cs)]; +do_escape([C|Cs]) when C >= $\000, C =< $\s -> + escape(Cs); +do_escape([C|Cs]) -> + [escape_char(C)|escape(Cs)]. + +escape_char($n) -> $\n; %\n = LF +escape_char($r) -> $\r; %\r = CR +escape_char($t) -> $\t; %\t = TAB +escape_char($v) -> $\v; %\v = VT +escape_char($b) -> $\b; %\b = BS +escape_char($f) -> $\f; %\f = FF +escape_char($e) -> $\e; %\e = ESC +escape_char($s) -> $\s; %\s = SPC +escape_char($d) -> $\d; %\d = DEL +escape_char(C) -> C. diff --git a/src/rebar.app.src b/src/rebar.app.src index 6058efc..40d953b 100644 --- a/src/rebar.app.src +++ b/src/rebar.app.src @@ -30,7 +30,6 @@ relx, cf, inets, - hex_core, eunit_formatters]}, {env, [ %% Default log level diff --git a/src/rebar_hex_repos.erl b/src/rebar_hex_repos.erl index babaa32..ed5b1a8 100644 --- a/src/rebar_hex_repos.erl +++ b/src/rebar_hex_repos.erl @@ -20,6 +20,7 @@ api_url => binary(), api_key => binary(), repo_url => binary(), + repo_key => binary(), repo_public_key => binary(), repo_verify => binary(), repo_verify_origin => binary()}. @@ -68,17 +69,21 @@ repos(HexConfig) -> merge_repos(RepoList ++ [HexDefaultConfig]) end. -%% merge repos must add a field repo_name to work with hex_core 0.4.0 +%% merge repos must add a field repo_name to work with r3_hex_core 0.5.0 -spec merge_repos([repo()]) -> [repo()]. merge_repos(Repos) -> - lists:foldl(fun(R=#{name := Name}, ReposAcc) -> - %% private organizations include the parent repo before a : + lists:foldl(fun(R = #{name := Name}, ReposAcc) -> + %% private orgs are in the format of <<"parent:org">> case rebar_string:split(Name, <<":">>) of [Repo, Org] -> - %% hex_core uses repo_name for parent + + %% We set the repo_organization and api_organization to org + %% for fetching and publishing private packages. update_repo_list(R#{name => Name, - repo_name => Repo, - organization => Org, + repo_name => Org, + repo_organization => Org, + api_organization => Org, + api_repository => Org, parent => Repo}, ReposAcc); _ -> update_repo_list(R#{repo_name => Name}, ReposAcc) @@ -86,15 +91,15 @@ merge_repos(Repos) -> end, [], Repos). update_organizations(Repos) -> - lists:map(fun(Repo=#{organization := Organization, + lists:map(fun(Repo=#{repo_name := RepoName, parent := ParentName}) -> {ok, Parent} = get_repo_config(ParentName, Repos), ParentRepoUrl = rebar_utils:to_list(maps:get(repo_url, Parent)), - {ok, RepoUrl} = + {ok, _RepoUrl} = rebar_utils:url_append_path(ParentRepoUrl, - filename:join("repos", rebar_utils:to_list(Organization))), + filename:join("repos", rebar_utils:to_list(RepoName))), %% still let the organization config override this constructed repo url - maps:merge(Parent#{repo_url => rebar_utils:to_binary(RepoUrl)}, Repo); + maps:merge(Parent#{repo_url => rebar_utils:to_binary(ParentRepoUrl)}, Repo); (Repo) -> Repo end, Repos). @@ -107,7 +112,7 @@ update_repo_list(R, []) -> [R]. default_repo() -> - HexDefaultConfig = hex_core:default_config(), + HexDefaultConfig = r3_hex_core:default_config(), HexDefaultConfig#{name => ?PUBLIC_HEX_REPO, repo_verify_origin => repo_verify_origin()}. repo_verify_origin() -> diff --git a/src/rebar_packages.erl b/src/rebar_packages.erl index fc68cab..a260e47 100644 --- a/src/rebar_packages.erl +++ b/src/rebar_packages.erl @@ -31,7 +31,7 @@ format_error({missing_package, Pkg}) -> -spec get(rebar_hex_repos:repo(), binary()) -> {ok, map()} | {error, term()}. get(Config, Name) -> - try hex_api_package:get(Config, Name) of + try r3_hex_api_package:get(Config, Name) of {ok, {200, _Headers, PkgInfo}} -> {ok, PkgInfo}; {ok, {404, _, _}} -> @@ -233,7 +233,7 @@ parse_checksum(Checksum) -> update_package(Name, RepoConfig=#{name := Repo}, State) -> ?MODULE:verify_table(State), - try hex_repo:get_package(get_package_repo_config(RepoConfig), Name) of + try r3_hex_repo:get_package(get_package_repo_config(RepoConfig), Name) of {ok, {200, _Headers, Releases}} -> _ = insert_releases(Name, Releases, Repo, ?PACKAGE_TABLE), {ok, RegistryDir} = rebar_packages:registry_dir(State), @@ -244,7 +244,7 @@ update_package(Name, RepoConfig=#{name := Repo}, State) -> fail; Error -> ?DEBUG("Hex get_package request failed: ~p", [Error]), - %% TODO: add better log message. hex_core should export a format_error + %% TODO: add better log message. r3_hex_core should export a format_error ?WARN("Failed to update package from repo ~ts", [Repo]), fail catch @@ -255,8 +255,6 @@ update_package(Name, RepoConfig=#{name := Repo}, State) -> get_package_repo_config(RepoConfig=#{mirror_of := Repo}) -> get_package_repo_config(maps:remove(mirror_of, RepoConfig#{name => Repo})); -get_package_repo_config(RepoConfig=#{read_key := Key}) -> - get_package_repo_config(maps:remove(read_key, RepoConfig#{repo_key => Key})); get_package_repo_config(RepoConfig) -> RepoConfig. diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 823b7fc..5de2437 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -29,7 +29,7 @@ -spec init(atom(), rebar_state:t()) -> {ok, rebar_resource_v2:resource()}. init(Type, State) -> {ok, Vsn} = application:get_key(rebar, vsn), - BaseConfig = #{http_adapter => hex_http_httpc, + BaseConfig = #{http_adapter => r3_hex_http_httpc, http_user_agent_fragment => <<"(rebar3/", (list_to_binary(Vsn))/binary, ") (httpc)">>, http_adapter_config => #{profile => rebar}}, @@ -148,7 +148,7 @@ format_error({bad_registry_checksum, Name, Vsn, Expected, Found}) -> -> {ok, cached} | {ok, binary(), binary()} | error. request(Config, Name, Version, ETag) -> Config1 = Config#{http_etag => ETag}, - try hex_repo:get_tarball(Config1, Name, Version) of + try r3_hex_repo:get_tarball(Config1, Name, Version) of {ok, {200, #{<<"etag">> := ETag1}, Tarball}} -> {ok, Tarball, ETag1}; {ok, {304, _Headers, _}} -> @@ -248,7 +248,7 @@ serve_from_cache(TmpDir, CachePath, Pkg) -> Res :: ok | {error,_} | {bad_registry_checksum, integer(), integer()}. serve_from_memory(TmpDir, Binary, {pkg, _Name, _Vsn, Hash, _RepoConfig}) -> RegistryChecksum = list_to_integer(binary_to_list(Hash), 16), - case hex_tarball:unpack(Binary, TmpDir) of + case r3_hex_tarball:unpack(Binary, TmpDir) of {ok, #{checksum := <<Checksum:256/big-unsigned>>}} when RegistryChecksum =/= Checksum -> ?DEBUG("Expected hash ~64.16.0B does not match checksum of fetched package ~64.16.0B", [RegistryChecksum, Checksum]), diff --git a/src/rebar_utils.erl b/src/rebar_utils.erl index 11add61..f1e440a 100644 --- a/src/rebar_utils.erl +++ b/src/rebar_utils.erl @@ -915,7 +915,7 @@ url_append_path(Url, ExtraPath) -> case http_uri:parse(Url) of {ok, {Scheme, UserInfo, Host, Port, Path, Query}} -> {ok, lists:append([atom_to_list(Scheme), "://", UserInfo, Host, ":", integer_to_list(Port), - filename:join(Path, ExtraPath), "?", Query])}; + filename:join(Path, ExtraPath), Query])}; _ -> error end. diff --git a/test/mock_pkg_resource.erl b/test/mock_pkg_resource.erl index a169efd..a176e88 100644 --- a/test/mock_pkg_resource.erl +++ b/test/mock_pkg_resource.erl @@ -100,7 +100,7 @@ mock_download(Opts) -> <<"version">> => Vsn}, Files = all_files(rebar_app_info:dir(AppInfo1)), - {ok, {Tarball, _Checksum}} = hex_tarball:create(Metadata, archive_names(Dir, Files)), + {ok, {Tarball, _Checksum}} = r3_hex_tarball:create(Metadata, archive_names(Dir, Files)), Archive = filename:join([Dir, TarApp]), file:write_file(Archive, Tarball), diff --git a/test/rebar_pkg_SUITE.erl b/test/rebar_pkg_SUITE.erl index ad06abd..383f9ef 100644 --- a/test/rebar_pkg_SUITE.erl +++ b/test/rebar_pkg_SUITE.erl @@ -97,7 +97,7 @@ init_per_testcase(bad_disconnect=Name, Config0) -> {pkg, Pkg} | Config0], Config = mock_config(Name, Config1), - meck:expect(hex_repo, get_tarball, fun(_, _, _) -> + meck:expect(r3_hex_repo, get_tarball, fun(_, _, _) -> {error, econnrefused} end), Config; @@ -281,8 +281,8 @@ mock_config(Name, Config) -> end, AllDeps), - meck:new(hex_repo, [passthrough]), - meck:expect(hex_repo, get_package, + meck:new(r3_hex_repo, [passthrough]), + meck:expect(r3_hex_repo, get_package, fun(_Config, PkgName) -> Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}), Releases = @@ -303,7 +303,7 @@ mock_config(Name, Config) -> end), meck:expect(rebar_state, resources, fun(_State) -> - DefaultConfig = hex_core:default_config(), + DefaultConfig = r3_hex_core:default_config(), [rebar_resource_v2:new(pkg, rebar_pkg_resource, #{repos => [DefaultConfig#{name => <<"hexpm">>}], base_config => #{}})] @@ -325,7 +325,7 @@ mock_config(Name, Config) -> PkgFile = <<Pkg/binary, "-", Vsn/binary, ".tar">>, {ok, PkgContents} = file:read_file(filename:join(?config(data_dir, Config), PkgFile)), - meck:expect(hex_repo, get_tarball, fun(_, _, _) when GoodCache -> + meck:expect(r3_hex_repo, get_tarball, fun(_, _, _) when GoodCache -> {ok, {304, #{<<"etag">> => ?good_etag}, <<>>}}; (_, _, _) -> {ok, {200, #{<<"etag">> => ?good_etag}, PkgContents}} diff --git a/test/rebar_pkg_alias_SUITE.erl b/test/rebar_pkg_alias_SUITE.erl index 1ef5a34..bfa5058 100644 --- a/test/rebar_pkg_alias_SUITE.erl +++ b/test/rebar_pkg_alias_SUITE.erl @@ -241,8 +241,8 @@ mock_config(Name, Config) -> end, AllDeps), - meck:new(hex_repo, [passthrough]), - meck:expect(hex_repo, get_package, + meck:new(r3_hex_repo, [passthrough]), + meck:expect(r3_hex_repo, get_package, fun(_Config, PkgName) -> Matches = ets:match_object(Tid, {{PkgName,'_'}, '_'}), Releases = @@ -254,7 +254,7 @@ mock_config(Name, Config) -> {ok, {200, #{}, Releases}} end), - meck:expect(hex_repo, get_tarball, fun(_, _, _) -> + meck:expect(r3_hex_repo, get_tarball, fun(_, _, _) -> {ok, {304, #{<<"etag">> => EtagGood}, <<>>}} end), diff --git a/test/rebar_pkg_repos_SUITE.erl b/test/rebar_pkg_repos_SUITE.erl index 874da71..8446bbf 100644 --- a/test/rebar_pkg_repos_SUITE.erl +++ b/test/rebar_pkg_repos_SUITE.erl @@ -291,14 +291,23 @@ organization_merging(_Config) -> ?assertMatch({ok, #resource{state=#{repos := [#{name := <<"hexpm:repo-1">>, parent := <<"hexpm">>, + repo_name := <<"repo-1">>, + api_repository := <<"repo-1">>, + repo_organization := <<"repo-1">>, read_key := <<"read key">>, write_key := <<"write key hexpm">>}, #{name := <<"hexpm:repo-2">>, parent := <<"hexpm">>, + repo_name := <<"repo-2">>, + api_repository := <<"repo-2">>, + repo_organization := <<"repo-2">>, read_key := <<"read key 2">>, repos_key := <<"repos key 2">>, write_key := <<"write key 2">>}, #{name := <<"hexpm">>, + repo_name := <<"hexpm">>, + api_repository := undefined, + repo_organization := undefined, write_key := <<"write key hexpm">>}]}}}, rebar_pkg_resource:init(pkg, State)), diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index 944efa0..b4f0043 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -511,7 +511,7 @@ package_app(AppDir, DestDir, PkgName, PkgVsn) -> Files = lists:zip([filename:join("src", F) || F <- Fs], [filename:join(AppSrc,F) || F <- Fs]), Metadata = #{<<"app">> => list_to_binary(PkgName), <<"version">> => list_to_binary(PkgVsn)}, - {ok, {Tarball, <<Checksum:256/big-unsigned-integer>>}} = hex_tarball:create(Metadata, Files), + {ok, {Tarball, <<Checksum:256/big-unsigned-integer>>}} = r3_hex_tarball:create(Metadata, Files), Name = PkgName++"-"++PkgVsn++".tar", Archive = filename:join(DestDir, Name), diff --git a/test/rebar_utils_SUITE.erl b/test/rebar_utils_SUITE.erl index 233fcff..4901f40 100644 --- a/test/rebar_utils_SUITE.erl +++ b/test/rebar_utils_SUITE.erl @@ -33,7 +33,8 @@ sh_does_not_miss_messages/1, tup_merge/1, proxy_auth/1, - is_list_of_strings/1]). + is_list_of_strings/1, + url_append_path/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -49,7 +50,7 @@ all() -> [{group, args_to_tasks}, sh_does_not_miss_messages, tup_merge, - proxy_auth, is_list_of_strings]. + proxy_auth, is_list_of_strings, url_append_path]. groups() -> [{args_to_tasks, [], [empty_arglist, @@ -319,3 +320,8 @@ is_list_of_strings(_Config) -> ?assert(rebar_utils:is_list_of_strings([])), ?assert(rebar_utils:is_list_of_strings("")), ?assert(rebar_utils:is_list_of_strings("foo") == false). + +url_append_path(_Config) -> + ?assertEqual({ok, "https://repo.hex.pm:443/repos/org"}, rebar_utils:url_append_path("https://repo.hex.pm", "/repos/org")), + ?assertEqual({ok, "https://repo.hex.pm:443/repos/org?foo=bar"}, rebar_utils:url_append_path("https://repo.hex.pm", + "/repos/org?foo=bar")). |