You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 regels
4.6 KiB

10 jaren geleden
10 jaren geleden
10 jaren geleden
  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. -include("rebar.hrl").
  10. lock(_AppDir, Source) ->
  11. Source.
  12. needs_update(Dir, {pkg, _Name, Vsn}) ->
  13. [AppInfo] = rebar_app_discover:find_apps([Dir], all),
  14. case rebar_app_info:original_vsn(AppInfo) =:= ec_cnv:to_list(Vsn) of
  15. true ->
  16. false;
  17. false ->
  18. true
  19. end.
  20. download(TmpDir, Pkg={pkg, Name, Vsn}, State) ->
  21. CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
  22. PackageDir = rebar_packages:package_dir(State),
  23. Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>),
  24. CachePath = filename:join(PackageDir, Package),
  25. Url = string:join([CDN, Package], "/"),
  26. cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State).
  27. cached_download(TmpDir, CachePath, Pkg, Url, ETag, State) ->
  28. case request(Url, ETag) of
  29. {ok, cached} ->
  30. serve_from_cache(TmpDir, CachePath, Pkg, State);
  31. {ok, Body, NewETag} ->
  32. serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body, State);
  33. error when ETag =/= false ->
  34. ?DEBUG("Download ~s error, using ~s from cache", [Url, CachePath]),
  35. serve_from_cache(TmpDir, CachePath, Pkg, State);
  36. error ->
  37. request_failed
  38. end.
  39. serve_from_cache(TmpDir, CachePath, Pkg, State) ->
  40. {Files, Contents, Version, Meta} = extract(TmpDir, CachePath),
  41. case checksums(Pkg, Files, Contents, Version, Meta, State) of
  42. {Chk, Chk, Chk} ->
  43. ok = erl_tar:extract({binary, Contents}, [{cwd, TmpDir}, compressed]),
  44. {ok, true};
  45. {_Bin, Chk, Chk} ->
  46. ?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]),
  47. {failed_extract, CachePath};
  48. {Chk, _Reg, Chk} ->
  49. ?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]),
  50. {bad_registry_checksum, CachePath};
  51. {_Bin, _Reg, _Tar} ->
  52. ?DEBUG("Checksums: registry: ~p, pkg: ~p, meta: ~p", [_Reg, _Bin, _Tar]),
  53. {bad_checksum, CachePath}
  54. end.
  55. serve_from_download(TmpDir, CachePath, Package, ETag, Binary, State) ->
  56. ?DEBUG("Writing ~p to cache at ~s", [Package, CachePath]),
  57. file:write_file(CachePath, Binary),
  58. case etag(CachePath) of
  59. ETag ->
  60. serve_from_cache(TmpDir, CachePath, Package, State);
  61. FileETag ->
  62. ?DEBUG("Downloaded file ~s ETag ~s doesn't match returned ETag ~s", [CachePath, ETag, FileETag]),
  63. {bad_download, CachePath}
  64. end.
  65. extract(TmpDir, CachePath) ->
  66. ec_file:mkdir_p(TmpDir),
  67. {ok, Files} = erl_tar:extract(CachePath, [memory]),
  68. {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files),
  69. {"VERSION", Version} = lists:keyfind("VERSION", 1, Files),
  70. {"metadata.config", Meta} = lists:keyfind("metadata.config", 1, Files),
  71. {Files, Contents, Version, Meta}.
  72. checksums(Pkg, Files, Contents, Version, Meta, State) ->
  73. Blob = <<Version/binary, Meta/binary, Contents/binary>>,
  74. <<X:256/big-unsigned>> = crypto:hash(sha256, Blob),
  75. BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))),
  76. RegistryChecksum = rebar_packages:registry_checksum(Pkg, State),
  77. {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files),
  78. {BinChecksum, RegistryChecksum, TarChecksum}.
  79. make_vsn(_) ->
  80. {error, "Replacing version of type pkg not supported."}.
  81. request(Url, ETag) ->
  82. case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]},
  83. [{relaxed, true}],
  84. [{body_format, binary}]) of
  85. {ok, {{_Version, 200, _Reason}, Headers, Body}} ->
  86. ?DEBUG("Successfully downloaded ~s", [Url]),
  87. {"etag", ETag1} = lists:keyfind("etag", 1, Headers),
  88. {ok, Body, string:strip(ETag1, both, $")};
  89. {ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
  90. ?DEBUG("Cached copy of ~s still valid", [Url]),
  91. {ok, cached};
  92. {ok, {{_Version, Code, _Reason}, _Headers, _Body}} ->
  93. ?DEBUG("Request to ~p failed: status code ~p", [Url, Code]),
  94. error;
  95. {error, Reason} ->
  96. ?DEBUG("Request to ~p failed: ~p", [Url, Reason]),
  97. error
  98. end.
  99. etag(Path) ->
  100. case file:read_file(Path) of
  101. {ok, Binary} ->
  102. <<X:128/big-unsigned-integer>> = crypto:hash(md5, Binary),
  103. string:to_lower(lists:flatten(io_lib:format("~32.16.0b", [X])));
  104. {error, _} ->
  105. false
  106. end.