Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1use anyhow::bail;
2use backoff::{retry, ExponentialBackoff};
3use data_encoding::BASE64;
4use digest::Digest;
5use isahc::{
6 config::{CaCertificate, Configurable, RedirectPolicy, SslOption},
7 Body, Request, RequestExt,
8};
9use log::info;
10use nix_nar::{Encoder, NarError};
11use serde_json::{Map, Value};
12use sha2::Sha256;
13use std::{
14 env,
15 io::{self, Read},
16 path::Path,
17};
18use url::Url;
19
20pub fn get_url(url: &Url) -> Result<Body, anyhow::Error> {
21 let mut request = Request::get(url.as_str()).redirect_policy(RedirectPolicy::Limit(10));
22
23 // Respect SSL_CERT_FILE if environment variable exists
24 if let Ok(ssl_cert_file) = env::var("SSL_CERT_FILE") {
25 if Path::new(&ssl_cert_file).exists() {
26 // When file exists, use it. NIX_SSL_CERT_FILE will still override.
27 request = request.ssl_ca_certificate(CaCertificate::file(ssl_cert_file));
28 } else if env::var("outputHash").is_ok() {
29 // When file does not exist, assume we are downloading in a FOD and
30 // therefore do not need to check certificates, since the output is
31 // already hashed.
32 request = request.ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS);
33 }
34 }
35
36 // Respect NIX_NPM_TOKENS environment variable, which should be a JSON mapping in the shape of:
37 // `{ "registry.example.com": "example-registry-bearer-token", ... }`
38 if let Some(host) = url.host_str() {
39 if let Ok(npm_tokens) = env::var("NIX_NPM_TOKENS") {
40 if let Ok(tokens) = serde_json::from_str::<Map<String, Value>>(&npm_tokens) {
41 if let Some(token) = tokens.get(host).and_then(serde_json::Value::as_str) {
42 info!("Found NPM token for {}. Adding authorization header to request.", host);
43 request = request.header("Authorization", format!("Bearer {token}"));
44 }
45 }
46 }
47 }
48
49 let res = request.body(())?.send()?;
50 if !res.status().is_success() {
51 if res.status().is_client_error() {
52 bail!("Client error: {}", res.status());
53 }
54 if res.status().is_server_error() {
55 bail!("Server error: {}", res.status());
56 }
57 bail!("{}", res.status());
58 }
59 Ok(res.into_body())
60}
61
62pub fn get_url_body_with_retry(url: &Url) -> Result<Vec<u8>, anyhow::Error> {
63 retry(ExponentialBackoff::default(), || {
64 get_url(url)
65 .and_then(|mut body| {
66 let mut buf = Vec::new();
67
68 body.read_to_end(&mut buf)?;
69
70 Ok(buf)
71 })
72 .map_err(|err| match err.downcast_ref::<isahc::Error>() {
73 Some(isahc_err) => {
74 if isahc_err.is_network() || isahc_err.is_timeout() {
75 backoff::Error::transient(err)
76 } else {
77 backoff::Error::permanent(err)
78 }
79 }
80 None => backoff::Error::permanent(err),
81 })
82 })
83 .map_err(|backoff_err| match backoff_err {
84 backoff::Error::Permanent(err)
85 | backoff::Error::Transient {
86 err,
87 retry_after: _,
88 } => err,
89 })
90}
91
92pub fn make_sri_hash(path: &Path) -> Result<String, NarError> {
93 let mut encoder = Encoder::new(path)?;
94 let mut hasher = Sha256::new();
95
96 io::copy(&mut encoder, &mut hasher)?;
97
98 Ok(format!("sha256-{}", BASE64.encode(&hasher.finalize())))
99}