anchor.diff:          ,etag/1
           ,ssl_opts/1]).
   
  +%% Exported for ct
   -export([store_etag_in_cache/2]).
   
   -include("rebar.hrl").
  
positive.code: checksums(Pkg={pkg, _Name, _Vsn, Hash}, Files, Contents, Version, Meta, State) ->
      Blob = <<Version/binary, Meta/binary, Contents/binary>>,
      <<X:256/big-unsigned>> = crypto:hash(sha256, Blob),
      BinChecksum = list_to_binary(rebar_string:uppercase(lists:flatten(io_lib:format("~64.16.0b", [X])))),
      RegistryChecksum = rebar_packages:registry_checksum(Pkg, State),
      {"CHECKSUM", TarChecksum} = lists:keyfind("CHECKSUM", 1, Files),
      {Hash, BinChecksum, RegistryChecksum, TarChecksum}.
  
  -spec make_vsn(Vsn) -> Res when
        Vsn :: any(),
        Res :: {'error',[1..255,...]}.
  make_vsn(_) ->
      {error, "Replacing version of type pkg not supported."}.
  
  -spec request(Url, ETag) -> Res when
        Url :: string(),
        ETag :: false | string(),
        Res :: 'error' | {ok, cached} | {ok, any(), string()}.
  request(Url, ETag) ->
      HttpOptions = [{ssl, ssl_opts(Url)}, {relaxed, true} | rebar_utils:get_proxy_auth()],
      case httpc:request(get, {Url, [{"if-none-match", "\"" ++ ETag ++ "\""} || ETag =/= false]++
                               [{"User-Agent", rebar_utils:user_agent()}]},
                         HttpOptions,
                         [{body_format, binary}],
                         rebar) of
          {ok, {{_Version, 200, _Reason}, Headers, Body}} ->
              ?DEBUG("Successfully downloaded ~ts", [Url]),
              {"etag", ETag1} = lists:keyfind("etag", 1, Headers),
              {ok, Body, rebar_string:trim(ETag1, both, [$"])};
          {ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
              ?DEBUG("Cached copy of ~ts 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.
  
  -spec etag(Path) -> Res when
        Path :: file:name(),
        Res :: false | string().
  etag(Path) ->
      case file:read_file(Path) of
          {ok, Bin} ->
              binary_to_list(Bin);
          {error, _} ->
              false
      end.
  
  %% @doc returns the SSL options adequate for the project based on
  %% its configuration, including for validation of certs.
  -spec ssl_opts(Url) -> Res when
        Url :: string(),
        Res :: proplists:proplist().
  ssl_opts(Url) ->
      case get_ssl_config() of
          ssl_verify_enabled ->
              ssl_opts(ssl_verify_enabled, Url);
          ssl_verify_disabled ->
              [{verify, verify_none}]
      end.
  
  %% @doc returns the SSL options adequate for the project based on
  %% its configuration, including for validation of certs.
  -spec ssl_opts(Enabled, Url) -> Res when
        Enabled :: atom(),
        Url :: string(),
  
negative.code:     ETagFile = binary_to_list(<<Name/binary, "-", Vsn/binary, ".etag">>),
      CachePath = filename:join(PackageDir, Package),
      ETagPath = filename:join(PackageDir, ETagFile),
      case rebar_utils:url_append_path(CDN, filename:join(?REMOTE_PACKAGE_DIR,
                                                          Package)) of
          {ok, Url} ->
              cached_download(TmpDir, CachePath, Pkg, Url, etag(ETagPath), State,
                              ETagPath, UpdateETag);
          _ ->
              {fetch_fail, Name, Vsn}
      end.
  
  %%------------------------------------------------------------------------------
  %% @doc
  %% Implementation of rebar_resource make_vsn callback.
  %% Returns {error, string()} as this operation is not supported for pkg sources.
  %% @end
  %%------------------------------------------------------------------------------
  -spec make_vsn(Vsn) -> Res when
        Vsn :: any(),
        Res :: {'error',[1..255,...]}.
  make_vsn(_) ->
      {error, "Replacing version of type pkg not supported."}.
  
  %%------------------------------------------------------------------------------
  %% @doc
  %% Download the pkg belonging to the given address. If the etag of the pkg
  %% is the same what we stored in the etag file previously return {ok, cached},
  %% if the file has changed (so the etag is not the same anymore) return
  %% {ok, Contents, NewEtag}, otherwise if some error occured return error.
  %% @end
  %%------------------------------------------------------------------------------
  -spec request(Url, ETag) -> Res when
        Url :: string(),
        ETag :: false | string(),
        Res :: 'error' | {ok, cached} | {ok, any(), string()}.
  request(Url, ETag) ->
      HttpOptions = [{ssl, ssl_opts(Url)},
                     {relaxed, true} | rebar_utils:get_proxy_auth()],
      case httpc:request(get, {Url, [{"if-none-match", "\"" ++ ETag ++ "\""}
                                     || ETag =/= false] ++
                               [{"User-Agent", rebar_utils:user_agent()}]},
                         HttpOptions, [{body_format, binary}], rebar) of
          {ok, {{_Version, 200, _Reason}, Headers, Body}} ->
              ?DEBUG("Successfully downloaded ~ts", [Url]),
              {"etag", ETag1} = lists:keyfind("etag", 1, Headers),
              {ok, Body, rebar_string:trim(ETag1, both, [$"])};
          {ok, {{_Version, 304, _Reason}, _Headers, _Body}} ->
              ?DEBUG("Cached copy of ~ts 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.
  
  %%------------------------------------------------------------------------------
  %% @doc
  %% Read the etag belonging to the pkg file from the cache directory. The etag
  %% is stored in a separate file when the etag belonging to the package is
  %% returned from the hexpm server. The name is package-vsn.etag.
  %% @end
  %%------------------------------------------------------------------------------
  -spec etag(Path) -> Res when
        Path :: file:name(),
        Res :: false | string().
  etag(Path) ->
      case file:read_file(Path) of
          {ok, Bin} ->
              binary_to_list(Bin);
          {error, _} ->
              false
      end.
  
  %%------------------------------------------------------------------------------
  %% @doc
  %% Return the SSL options adequate for the project based on
  %% its configuration, including for validation of certs.
  %% @end
  %%------------------------------------------------------------------------------
  -spec ssl_opts(Url) -> Res when
        Url :: string(),
        Res :: proplists:proplist().
  ssl_opts(Url) ->
      case get_ssl_config() of
          ssl_verify_enabled ->
              ssl_opts(ssl_verify_enabled, Url);
          ssl_verify_disabled ->
              [{verify, verify_none}]
      end.
  
  %%------------------------------------------------------------------------------
  %% @doc
  %% Store the given etag in the .cache folder. The name is pakckage-vsn.etag.
  %% @end
  %%------------------------------------------------------------------------------
  -spec store_etag_in_cache(File, ETag) -> Res when
        File :: file:name(),
        ETag :: string(),
        Res :: ok.
  store_etag_in_cache(Path, ETag) ->
      ok = file:write_file(Path, ETag).
  
  %%%=============================================================================
  %%% Private functions
  %%%=============================================================================
  -spec cached_download(TmpDir, CachePath, Pkg, Url, ETag, State, ETagPath,
                        UpdateETag) -> Res when
        TmpDir :: file:name(),
        CachePath :: file:name(),
        Pkg :: {pkg, Name :: binary(), Vsn :: binary(), Hash :: binary()},
  