api-v2/deps/esbuild/lib/esbuild/npm_registry.ex
2025-04-16 10:03:13 -03:00

163 lines
4.5 KiB
Elixir

defmodule Esbuild.NpmRegistry do
@moduledoc false
require Logger
# source: https://registry.npmjs.org/-/npm/v1/keys
@public_keys %{
"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA" => """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i
6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==
-----END PUBLIC KEY-----
""",
"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U" => """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY6Ya7W++7aUPzvMTrezH6Ycx3c+H
OKYCcNGybJZSCJq/fd7Qa8uuAKtdIkUQtQiEKERhAmE5lMMJhP8OkDOa2g==
-----END PUBLIC KEY-----
"""
}
@base_url "https://registry.npmjs.org"
def fetch_package!(name, version) do
url = "#{@base_url}/#{name}/#{version}"
scheme = URI.parse(url).scheme
Logger.debug("Downloading esbuild from #{url}")
{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)
if proxy = proxy_for_scheme(scheme) do
%{host: host, port: port} = URI.parse(proxy)
Logger.debug("Using #{String.upcase(scheme)}_PROXY: #{proxy}")
set_option = if "https" == scheme, do: :https_proxy, else: :proxy
:httpc.set_options([{set_option, {{String.to_charlist(host), port}, []}}])
end
%{
"_id" => id,
"dist" => %{
"integrity" => integrity,
"signatures" => signatures,
"tarball" => tarball
}
} =
fetch_file!(url)
|> Jason.decode!()
%{"keyid" => keyid, "sig" => signature} =
signatures
|> Enum.find(fn %{"keyid" => keyid} -> is_map_key(@public_keys, keyid) end) ||
raise "missing signature"
verify_signature!("#{id}:#{integrity}", keyid, signature)
tar = fetch_file!(tarball)
[hash_alg, checksum] =
integrity
|> String.split("-")
verify_integrity!(tar, hash_alg, Base.decode64!(checksum))
tar
end
defp fetch_file!(url, retry \\ true) do
case {retry, do_fetch(url)} do
{_, {:ok, {{_, 200, _}, _headers, body}}} ->
body
{true, {:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}}
when inet in [:inet, :inet6] and
reason in [:ehostunreach, :enetunreach, :eprotonosupport, :nxdomain] ->
:httpc.set_options(ipfamily: fallback(inet))
fetch_file!(url, false)
other ->
raise """
couldn't fetch #{url}: #{inspect(other)}
You may also install the "esbuild" executable manually, \
see the docs: https://hexdocs.pm/esbuild
"""
end
end
defp fallback(:inet), do: :inet6
defp fallback(:inet6), do: :inet
defp do_fetch(url) do
scheme = URI.parse(url).scheme
url = String.to_charlist(url)
:httpc.request(
:get,
{url, []},
[
ssl: [
verify: :verify_peer,
# https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets
cacerts: :public_key.cacerts_get(),
depth: 2,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]
|> maybe_add_proxy_auth(scheme),
body_format: :binary
)
end
defp proxy_for_scheme("http") do
System.get_env("HTTP_PROXY") || System.get_env("http_proxy")
end
defp proxy_for_scheme("https") do
System.get_env("HTTPS_PROXY") || System.get_env("https_proxy")
end
defp maybe_add_proxy_auth(http_options, scheme) do
case proxy_auth(scheme) do
nil -> http_options
auth -> [{:proxy_auth, auth} | http_options]
end
end
defp proxy_auth(scheme) do
with proxy when is_binary(proxy) <- proxy_for_scheme(scheme),
%{userinfo: userinfo} when is_binary(userinfo) <- URI.parse(proxy),
[username, password] <- String.split(userinfo, ":") do
{String.to_charlist(username), String.to_charlist(password)}
else
_ -> nil
end
end
defp verify_signature!(message, key_id, signature) do
:public_key.verify(
message,
:sha256,
Base.decode64!(signature),
public_key(key_id)
) or raise "invalid signature"
end
defp verify_integrity!(binary, hash_alg, checksum) do
binary_checksum =
hash_alg
|> hash_alg_to_erlang()
|> :crypto.hash(binary)
binary_checksum == checksum or raise "invalid checksum"
end
defp public_key(key_id) do
[entry] = :public_key.pem_decode(@public_keys[key_id])
:public_key.pem_entry_decode(entry)
end
defp hash_alg_to_erlang("sha512"), do: :sha512
end