1use std::borrow::Cow;
2use std::sync::LazyLock;
3
4use anyhow::{Context, Result};
5use reqsign::aws::DefaultSigner as AwsDefaultSigner;
6use reqsign::azure::DefaultSigner as AzureDefaultSigner;
7use reqsign::google::DefaultSigner as GcsDefaultSigner;
8use tracing::debug;
9use url::{ParseError, Url};
10
11use uv_preview::{Preview, PreviewFeature};
12use uv_static::EnvVars;
13use uv_warnings::warn_user_once;
14
15use crate::Credentials;
16use crate::credentials::Token;
17use crate::index::is_path_prefix;
18use crate::realm::{Realm, RealmRef};
19
20static HUGGING_FACE_REALM: LazyLock<Realm> = LazyLock::new(|| {
22 let url = Url::parse("https://huggingface.co").expect("Failed to parse Hugging Face URL");
23 Realm::from(&url)
24});
25
26static HUGGING_FACE_TOKEN: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
28 let hf_token = std::env::var(EnvVars::HF_TOKEN)
30 .ok()
31 .map(String::into_bytes)
32 .filter(|token| !token.is_empty())?;
33
34 if std::env::var_os(EnvVars::UV_NO_HF_TOKEN).is_some() {
35 debug!("Ignoring Hugging Face token from environment due to `UV_NO_HF_TOKEN`");
36 return None;
37 }
38
39 debug!("Found Hugging Face token in environment");
40 Some(hf_token)
41});
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub(crate) struct HuggingFaceProvider;
46
47impl HuggingFaceProvider {
48 pub(crate) fn credentials_for(url: &Url) -> Option<Credentials> {
50 if RealmRef::from(url) == *HUGGING_FACE_REALM {
51 if let Some(token) = HUGGING_FACE_TOKEN.as_ref() {
52 return Some(Credentials::Bearer {
53 token: Token::new(token.clone()),
54 });
55 }
56 }
57 None
58 }
59}
60
61static S3_ENDPOINT_URL: LazyLock<Result<Option<Url>, ParseError>> =
63 LazyLock::new(|| endpoint_url(EnvVars::UV_S3_ENDPOINT_URL));
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub(crate) struct S3EndpointProvider;
68
69impl S3EndpointProvider {
70 pub(crate) fn is_s3_endpoint(url: &Url, preview: Preview) -> Result<bool> {
72 if let Some(s3_endpoint_url) = S3_ENDPOINT_URL
73 .as_ref()
74 .map_err(|error| *error)
75 .with_context(|| format!("Invalid `{}`", EnvVars::UV_S3_ENDPOINT_URL))?
76 {
77 if !preview.is_enabled(PreviewFeature::S3Endpoint) {
78 warn_user_once!(
79 "The `s3-endpoint` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
80 PreviewFeature::S3Endpoint
81 );
82 }
83
84 if is_endpoint_url(url, s3_endpoint_url) {
87 return Ok(true);
88 }
89 }
90 Ok(false)
91 }
92
93 pub(crate) fn create_signer() -> AwsDefaultSigner {
98 let region = std::env::var(EnvVars::AWS_REGION)
101 .map(Cow::Owned)
102 .unwrap_or_else(|_| {
103 std::env::var(EnvVars::AWS_DEFAULT_REGION)
104 .map(Cow::Owned)
105 .unwrap_or_else(|_| Cow::Borrowed("us-east-1"))
106 });
107 reqsign::aws::default_signer("s3", ®ion)
108 }
109}
110
111static GCS_ENDPOINT_URL: LazyLock<Result<Option<Url>, ParseError>> =
113 LazyLock::new(|| endpoint_url(EnvVars::UV_GCS_ENDPOINT_URL));
114
115#[derive(Debug, Clone, PartialEq, Eq)]
117pub(crate) struct GcsEndpointProvider;
118
119impl GcsEndpointProvider {
120 pub(crate) fn is_gcs_endpoint(url: &Url, preview: Preview) -> Result<bool> {
122 if let Some(gcs_endpoint_url) = GCS_ENDPOINT_URL
123 .as_ref()
124 .map_err(|error| *error)
125 .with_context(|| format!("Invalid `{}`", EnvVars::UV_GCS_ENDPOINT_URL))?
126 {
127 if !preview.is_enabled(PreviewFeature::GcsEndpoint) {
128 warn_user_once!(
129 "The `gcs-endpoint` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
130 PreviewFeature::GcsEndpoint
131 );
132 }
133
134 if is_endpoint_url(url, gcs_endpoint_url) {
137 return Ok(true);
138 }
139 }
140 Ok(false)
141 }
142
143 pub(crate) fn create_signer() -> GcsDefaultSigner {
148 reqsign::google::default_signer("storage.googleapis.com")
149 }
150}
151
152static AZURE_ENDPOINT_URL: LazyLock<Result<Option<Url>, ParseError>> =
154 LazyLock::new(|| endpoint_url(EnvVars::UV_AZURE_ENDPOINT_URL));
155
156#[derive(Debug, Clone, PartialEq, Eq)]
158pub struct AzureEndpointProvider;
159
160impl AzureEndpointProvider {
161 pub fn is_azure_endpoint(url: &Url, preview: Preview) -> Result<bool> {
163 if let Some(azure_endpoint_url) = AZURE_ENDPOINT_URL
164 .as_ref()
165 .map_err(|error| *error)
166 .with_context(|| format!("Invalid `{}`", EnvVars::UV_AZURE_ENDPOINT_URL))?
167 {
168 if !preview.is_enabled(PreviewFeature::AzureEndpoint) {
169 warn_user_once!(
170 "The `azure-endpoint` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
171 PreviewFeature::AzureEndpoint
172 );
173 }
174
175 if is_endpoint_url(url, azure_endpoint_url) {
178 return Ok(true);
179 }
180 }
181 Ok(false)
182 }
183
184 pub(crate) fn create_signer() -> AzureDefaultSigner {
189 reqsign::azure::default_signer()
190 }
191}
192
193fn endpoint_url(env_var: &str) -> Result<Option<Url>, ParseError> {
195 let Some(endpoint_url) = std::env::var(env_var).ok() else {
196 return Ok(None);
197 };
198 Url::parse(&endpoint_url).map(Some)
199}
200
201fn is_endpoint_url(url: &Url, endpoint_url: &Url) -> bool {
206 let endpoint_realm = RealmRef::from(endpoint_url);
207 let realm = RealmRef::from(url);
208 if realm != endpoint_realm && !realm.is_subdomain_of(endpoint_realm) {
209 return false;
210 }
211
212 is_path_prefix(endpoint_url.path(), url.path())
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_endpoint_url_matches_path_prefix() {
221 let endpoint_url = Url::parse("https://example.com/private").unwrap();
222
223 for url in [
224 "https://example.com/private",
225 "https://example.com/private/",
226 "https://example.com/private/packages/anyio.whl",
227 ] {
228 assert!(
229 is_endpoint_url(&Url::parse(url).unwrap(), &endpoint_url),
230 "Failed to match endpoint URL prefix: {url}"
231 );
232 }
233 }
234
235 #[test]
236 fn test_endpoint_url_rejects_partial_path_segments() {
237 let endpoint_url = Url::parse("https://example.com/private").unwrap();
238
239 for url in [
240 "https://example.com/public",
241 "https://example.com/private-bucket",
242 "https://example.com/privatebucket",
243 ] {
244 assert!(
245 !is_endpoint_url(&Url::parse(url).unwrap(), &endpoint_url),
246 "Should not match URL outside endpoint path: {url}"
247 );
248 }
249 }
250
251 #[test]
252 fn test_endpoint_url_matches_subdomain_with_path_prefix() {
253 let endpoint_url = Url::parse("https://example.com/private").unwrap();
254
255 assert!(is_endpoint_url(
256 &Url::parse("https://bucket.example.com/private/package.whl").unwrap(),
257 &endpoint_url
258 ));
259 assert!(!is_endpoint_url(
260 &Url::parse("https://bucket.example.com/public/package.whl").unwrap(),
261 &endpoint_url
262 ));
263 }
264
265 #[test]
266 fn test_endpoint_url_root_path_matches_all_paths() {
267 let endpoint_url = Url::parse("https://example.com").unwrap();
268
269 for url in [
270 "https://example.com/package.whl",
271 "https://bucket.example.com/package.whl",
272 ] {
273 assert!(
274 is_endpoint_url(&Url::parse(url).unwrap(), &endpoint_url),
275 "Failed to match URL under endpoint root: {url}"
276 );
277 }
278 }
279}