diff options
Diffstat (limited to 'src/rebar_pkg_resource.erl')
-rw-r--r-- | src/rebar_pkg_resource.erl | 99 |
1 files changed, 93 insertions, 6 deletions
diff --git a/src/rebar_pkg_resource.erl b/src/rebar_pkg_resource.erl index 3b44fc8..59ce0dc 100644 --- a/src/rebar_pkg_resource.erl +++ b/src/rebar_pkg_resource.erl @@ -23,12 +23,99 @@ needs_update(Dir, {pkg, _Name, Vsn}) -> true end. -download(Dir, {pkg, Name, Vsn}, State) -> - TmpFile = filename:join(Dir, "package.tar.gz"), - CDN = rebar_state:get(State, rebar_packages_cdn, "https://s3.amazonaws.com/s3.hex.pm/tarballs"), - Url = string:join([CDN, binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>)], "/"), - {ok, saved_to_file} = httpc:request(get, {Url, []}, [], [{stream, TmpFile}]), - {tarball, TmpFile}. +download(TmpDir, Pkg={pkg, Name, Vsn}, State) -> + CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN), + PackageDir = rebar_packages:package_dir(State), + Package = binary_to_list(<<Name/binary, "-", Vsn/binary, ".tar">>), + CachePath = filename:join(PackageDir, Package), + Url = string:join([CDN, Package], "/"), + cached_download(TmpDir, CachePath, Pkg, Url, etag(CachePath), State). + +cached_download(TmpDir, CachePath, Pkg, Url, ETag, State) -> + case request(Url, ETag) of + {ok, cached} -> + serve_from_cache(TmpDir, CachePath, Pkg, State); + {ok, Body, NewETag} -> + serve_from_download(TmpDir, CachePath, Pkg, NewETag, Body, State); + error when ETag =/= false -> + ?DEBUG("Download ~s error, using ~s from cache", [Url, CachePath]), + serve_from_cache(TmpDir, CachePath, Pkg, State); + error -> + request_failed + end. + +serve_from_cache(TmpDir, CachePath, Pkg, State) -> + {Files, Contents, Version, Meta} = extract(TmpDir, CachePath), + case checksums(Pkg, Files, Contents, Version, Meta, State) of + {Chk, Chk, Chk} -> + ok = erl_tar:extract({binary, Contents}, [{cwd, TmpDir}, compressed]), + {ok, true}; + {_Bin, Chk, Chk} -> + ?DEBUG("Checksums: registry: ~p, pkg: ~p", [Chk, _Bin]), + {failed_extract, CachePath}; + {Chk, _Reg, Chk} -> + ?DEBUG("Checksums: registry: ~p, pkg: ~p", [_Reg, Chk]), + {bad_registry_checksum, CachePath}; + {_Bin, _Reg, _Tar} -> + ?DEBUG("Checksums: registry: ~p, pkg: ~p, meta: ~p", [_Reg, _Bin, _Tar]), + {bad_checksum, CachePath} + end. + +serve_from_download(TmpDir, CachePath, Package, ETag, Binary, State) -> + ?DEBUG("Writing ~p to cache at ~s", [Package, CachePath]), + file:write_file(CachePath, Binary), + case etag(CachePath) of + ETag -> + serve_from_cache(TmpDir, CachePath, Package, State); + FileETag -> + ?DEBUG("Downloaded file ~s ETag ~s doesn't match returned ETag ~s", [CachePath, ETag, FileETag]), + {bad_download, CachePath} + end. + + +extract(TmpDir, CachePath) -> + ec_file:mkdir_p(TmpDir), + {ok, Files} = erl_tar:extract(CachePath, [memory]), + {"contents.tar.gz", Contents} = lists:keyfind("contents.tar.gz", 1, Files), + {"VERSION", Version} = lists:keyfind("VERSION", 1, Files), + {"metadata.config", Meta} = lists:keyfind("metadata.config", 1, Files), + {Files, Contents, Version, Meta}. + +checksums(Pkg, Files, Contents, Version, Meta, State) -> + Blob = <<Version/binary, Meta/binary, Contents/binary>>, + <<X:256/big-unsigned>> = crypto:hash(sha256, Blob), + BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))), + RegistryChecksum = rebar_packages:registry_checksum(Pkg, State), + {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files), + {BinChecksum, RegistryChecksum, TarChecksum}. make_vsn(_) -> {error, "Replacing version of type pkg not supported."}. + +request(Url, ETag) -> + case httpc:request(get, {Url, [{"if-none-match", ETag} || ETag =/= false]}, + [{relaxed, true}], + [{body_format, binary}]) of + {ok, {{_Version, 200, _Reason}, Headers, Body}} -> + ?DEBUG("Successfully downloaded ~s", [Url]), + {"etag", ETag1} = lists:keyfind("etag", 1, Headers), + {ok, Body, string:strip(ETag1, both, $")}; + {ok, {{_Version, 304, _Reason}, _Headers, _Body}} -> + ?DEBUG("Cached copy of ~s still valid", [Url]), + {ok, cached}; + {ok, {{_Version, Code, _Reason}, _Headers, _Body}} -> + ?DEBUG("Request to ~p failed: status code ~p", [Url, Code]), + error; + {error, Reason} -> + ?DEBUG("Request to ~p failed: ~p", [Url, Reason]), + error + end. + +etag(Path) -> + case file:read_file(Path) of + {ok, Binary} -> + <<X:128/big-unsigned-integer>> = crypto:hash(md5, Binary), + string:to_lower(lists:flatten(io_lib:format("~32.16.0b", [X]))); + {error, _} -> + false + end. |