Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

217 Zeilen
8.2 KiB

vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_pkg_resource).
  4. -behaviour(rebar_resource).
  5. -export([lock/2
  6. ,download/3
  7. ,needs_update/2
  8. ,make_vsn/1]).
  9. -export([request/2
  10. ,etag/1
  11. ,ssl_opts/1]).
  12. -include("rebar.hrl").
  13. -include_lib("public_key/include/OTP-PUB-KEY.hrl").
  14. lock(_AppDir, Source) ->
  15. Source.
  16. needs_update(Dir, {pkg, _Name, Vsn, _Hash}) ->
  17. [AppInfo] = rebar_app_discover:find_apps([Dir], all),
  18. case rebar_app_info:original_vsn(AppInfo) =:= rebar_utils:to_list(Vsn) of
  19. true ->
  20. false;
  21. false ->
  22. true
  23. end.
  24. download(TmpDir, Pkg={pkg, Name, Vsn, _Hash}, State) ->
  25. CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
  26. {ok, PackageDir} = rebar_packages:package_dir(State),
  27. Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
  28. CachePath = filename:join(PackageDir, Package),
  29. case rebar_utils:url_append_path(CDN, filename:join(?REMOTE_PACKAGE_DIR, Package)) of
  30. {ok, Url} ->
  31. cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State);
  32. _ ->
  33. {fetch_fail, Name, Vsn}
  34. end.
  35. cached_download(TmpDir, CachePath, Pkg={pkg, Name, Vsn, _Hash}, Url, ETag, State) ->
  36. case request(Url, ETag) of
  37. {ok, cached} ->
  38. ?INFO("Version cached at ~ts is up to date, reusing it", [CachePath]),
  39. serve_from_cache(TmpDir, CachePath, Pkg, State);
  40. {ok, Body, NewETag} ->
  41. ?INFO("Downloaded package, caching at ~ts", [CachePath]),
  42. serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body, State);
  43. error when ETag =/= false ->
  44. ?INFO("Download error, using cached file at ~ts", [CachePath]),
  45. serve_from_cache(TmpDir, CachePath, Pkg, State);
  46. error ->
  47. {fetch_fail, Name, Vsn}
  48. end.
  49. serve_from_cache(TmpDir, CachePath, Pkg, State) ->
  50. {Files, Contents, Version, Meta} = extract(TmpDir, CachePath),
  51. case checksums(Pkg, Files, Contents, Version, Meta, State) of
  52. {Chk, Chk, Chk, Chk} ->
  53. ok = erl_tar:extract({binary, Contents}, [{cwd, TmpDir}, compressed]),
  54. {ok, true};
  55. {_Hash, Chk, Chk, Chk} ->
  56. ?DEBUG("Expected hash ~p does not match checksums ~p", [_Hash, Chk]),
  57. {unexpected_hash, CachePath, _Hash, Chk};
  58. {Chk, _Bin, Chk, Chk} ->
  59. ?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]),
  60. {failed_extract, CachePath};
  61. {Chk, Chk, _Reg, Chk} ->
  62. ?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]),
  63. {bad_registry_checksum, CachePath};
  64. {_Hash, _Bin, _Reg, _Tar} ->
  65. ?DEBUG("Checksums: expected: ~p, registry: ~p, pkg: ~p, meta: ~p", [_Hash, _Reg, _Bin, _Tar]),
  66. {bad_checksum, CachePath}
  67. end.
  68. serve_from_download(TmpDir, CachePath, Package, ETag, Binary, State) ->
  69. ?DEBUG("Writing ~p to cache at ~ts", [Package, CachePath]),
  70. file:write_file(CachePath, Binary),
  71. case etag(CachePath) of
  72. ETag ->
  73. serve_from_cache(TmpDir, CachePath, Package, State);
  74. FileETag ->
  75. ?DEBUG("Downloaded file ~ts ETag ~ts doesn't match returned ETag ~ts", [CachePath, ETag, FileETag]),
  76. {bad_download, CachePath}
  77. end.
  78. extract(TmpDir, CachePath) ->
  79. ec_file:mkdir_p(TmpDir),
  80. {ok, Files} = erl_tar:extract(CachePath, [memory]),
  81. {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files),
  82. {"VERSION", Version} = lists:keyfind("VERSION", 1, Files),
  83. {"metadata.config", Meta} = lists:keyfind("metadata.config", 1, Files),
  84. {Files, Contents, Version, Meta}.
  85. checksums(Pkg={pkg, _Name, _Vsn, Hash}, Files, Contents, Version, Meta, State) ->
  86. Blob = <<Version/binary, Meta/binary, Contents/binary>>,
  87. <<X:256/big-unsigned>> = crypto:hash(sha256, Blob),
  88. BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))),
  89. RegistryChecksum = rebar_packages:registry_checksum(Pkg, State),
  90. {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files),
  91. {Hash, BinChecksum, RegistryChecksum, TarChecksum}.
  92. make_vsn(_) ->
  93. {error, "Replacing version of type pkg not supported."}.
  94. request(Url, ETag) ->
  95. HttpOptions = [{ssl, ssl_opts(Url)}, {relaxed, true} | rebar_utils:get_proxy_auth()],
  96. case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]++[{"User-Agent", rebar_utils:user_agent()}]},
  97. HttpOptions,
  98. [{body_format, binary}],
  99. rebar) of
  100. {ok, {{_Version, 200, _Reason}, Headers, Body}} ->
  101. ?DEBUG("Successfully downloaded ~ts", [Url]),
  102. {"etag", ETag1} = lists:keyfind("etag", 1, Headers),
  103. {ok, Body, string:strip(ETag1, both, $")};
  104. {ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
  105. ?DEBUG("Cached copy of ~ts still valid", [Url]),
  106. {ok, cached};
  107. {ok, {{_Version, Code, _Reason}, _Headers, _Body}} ->
  108. ?DEBUG("Request to ~p failed: status code ~p", [Url, Code]),
  109. error;
  110. {error, Reason} ->
  111. ?DEBUG("Request to ~p failed: ~p", [Url, Reason]),
  112. error
  113. end.
  114. etag(Path) ->
  115. case file:read_file(Path) of
  116. {ok, Binary} ->
  117. <<X:128/big-unsigned-integer>> = crypto:hash(md5, Binary),
  118. string:to_lower(lists:flatten(io_lib:format("~32.16.0b", [X])));
  119. {error, _} ->
  120. false
  121. end.
  122. %% @doc returns the SSL options adequate for the project based on
  123. %% its configuration, including for validation of certs.
  124. -spec ssl_opts(string()) -> [term()].
  125. ssl_opts(Url) ->
  126. case get_ssl_config() of
  127. ssl_verify_enabled ->
  128. ssl_opts(ssl_verify_enabled, Url);
  129. ssl_verify_disabled ->
  130. [{verify, verify_none}]
  131. end.
  132. %% @doc returns the SSL options adequate for the project based on
  133. %% its configuration, including for validation of certs.
  134. -spec ssl_opts(atom(), string()) -> [term()].
  135. ssl_opts(ssl_verify_enabled, Url) ->
  136. case check_ssl_version() of
  137. true ->
  138. {ok, {_, _, Hostname, _, _, _}} = http_uri:parse(rebar_utils:to_list(Url)),
  139. VerifyFun = {fun ssl_verify_hostname:verify_fun/3, [{check_hostname, Hostname}]},
  140. CACerts = certifi:cacerts(),
  141. [{verify, verify_peer}, {depth, 2}, {cacerts, CACerts}
  142. ,{partial_chain, fun partial_chain/1}, {verify_fun, VerifyFun}];
  143. false ->
  144. ?WARN("Insecure HTTPS request (peer verification disabled), please update to OTP 17.4 or later", []),
  145. [{verify, verify_none}]
  146. end.
  147. partial_chain(Certs) ->
  148. Certs1 = [{Cert, public_key:pkix_decode_cert(Cert, otp)} || Cert <- Certs],
  149. CACerts = certifi:cacerts(),
  150. CACerts1 = [public_key:pkix_decode_cert(Cert, otp) || Cert <- CACerts],
  151. case ec_lists:find(fun({_, Cert}) ->
  152. check_cert(CACerts1, Cert)
  153. end, Certs1) of
  154. {ok, Trusted} ->
  155. {trusted_ca, element(1, Trusted)};
  156. _ ->
  157. unknown_ca
  158. end.
  159. extract_public_key_info(Cert) ->
  160. ((Cert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subjectPublicKeyInfo).
  161. check_cert(CACerts, Cert) ->
  162. lists:any(fun(CACert) ->
  163. extract_public_key_info(CACert) == extract_public_key_info(Cert)
  164. end, CACerts).
  165. check_ssl_version() ->
  166. case application:get_key(ssl, vsn) of
  167. {ok, Vsn} ->
  168. parse_vsn(Vsn) >= {5, 3, 6};
  169. _ ->
  170. false
  171. end.
  172. get_ssl_config() ->
  173. GlobalConfigFile = rebar_dir:global_config(),
  174. Config = rebar_config:consult_file(GlobalConfigFile),
  175. case proplists:get_value(ssl_verify, Config, []) of
  176. false ->
  177. ssl_verify_disabled;
  178. _ ->
  179. ssl_verify_enabled
  180. end.
  181. parse_vsn(Vsn) ->
  182. version_pad(string:tokens(Vsn, ".-")).
  183. version_pad([Major]) ->
  184. {list_to_integer(Major), 0, 0};
  185. version_pad([Major, Minor]) ->
  186. {list_to_integer(Major), list_to_integer(Minor), 0};
  187. version_pad([Major, Minor, Patch]) ->
  188. {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)};
  189. version_pad([Major, Minor, Patch | _]) ->
  190. {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.