cargo_edit/
index.rs

1use tame_index::krate::IndexKrate;
2use tame_index::utils::flock::FileLock;
3
4use url::Url;
5
6use super::errors::*;
7
8#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
9pub enum CertsSource {
10    /// Use certs from Mozilla's root certificate store.
11    #[default]
12    Webpki,
13    /// Use certs from the system root certificate store.
14    Native,
15}
16
17pub struct IndexCache {
18    certs_source: CertsSource,
19    index: std::collections::HashMap<Url, AnyIndexCache>,
20}
21
22impl IndexCache {
23    #[inline]
24    pub fn new(certs_source: CertsSource) -> Self {
25        Self {
26            certs_source,
27            index: Default::default(),
28        }
29    }
30
31    /// Determines if the specified crate exists in the crates.io index
32    #[inline]
33    pub fn has_krate(&mut self, registry: &Url, name: &str) -> CargoResult<bool> {
34        self.index(registry)?.has_krate(name)
35    }
36
37    /// Determines if the specified crate version exists in the crates.io index
38    #[inline]
39    pub fn has_krate_version(
40        &mut self,
41        registry: &Url,
42        name: &str,
43        version: &str,
44    ) -> CargoResult<Option<bool>> {
45        self.index(registry)?.has_krate_version(name, version)
46    }
47
48    #[inline]
49    pub fn update_krate(&mut self, registry: &Url, name: &str) -> CargoResult<()> {
50        self.index(registry)?.update_krate(name);
51        Ok(())
52    }
53
54    pub fn krate(&mut self, registry: &Url, name: &str) -> CargoResult<Option<IndexKrate>> {
55        self.index(registry)?.krate(name)
56    }
57
58    pub fn index<'s>(&'s mut self, registry: &Url) -> CargoResult<&'s mut AnyIndexCache> {
59        if !self.index.contains_key(registry) {
60            let index = AnyIndex::open(registry, self.certs_source)?;
61            let index = AnyIndexCache::new(index);
62            self.index.insert(registry.clone(), index);
63        }
64        Ok(self.index.get_mut(registry).unwrap())
65    }
66}
67
68pub struct AnyIndexCache {
69    index: AnyIndex,
70    cache: std::collections::HashMap<String, Option<IndexKrate>>,
71}
72
73impl AnyIndexCache {
74    #[inline]
75    pub fn new(index: AnyIndex) -> Self {
76        Self {
77            index,
78            cache: std::collections::HashMap::new(),
79        }
80    }
81
82    /// Determines if the specified crate exists in the crates.io index
83    #[inline]
84    pub fn has_krate(&mut self, name: &str) -> CargoResult<bool> {
85        Ok(self.krate(name)?.map(|_| true).unwrap_or(false))
86    }
87
88    /// Determines if the specified crate version exists in the crates.io index
89    #[inline]
90    pub fn has_krate_version(&mut self, name: &str, version: &str) -> CargoResult<Option<bool>> {
91        let krate = self.krate(name)?;
92        Ok(krate.map(|ik| ik.versions.iter().any(|iv| iv.version == version)))
93    }
94
95    #[inline]
96    pub fn update_krate(&mut self, name: &str) {
97        self.cache.remove(name);
98    }
99
100    pub fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
101        if let Some(entry) = self.cache.get(name) {
102            return Ok(entry.clone());
103        }
104
105        let entry = self.index.krate(name)?;
106        self.cache.insert(name.to_owned(), entry.clone());
107        Ok(entry)
108    }
109}
110
111pub enum AnyIndex {
112    Local(LocalIndex),
113    Remote(RemoteIndex),
114}
115
116impl AnyIndex {
117    pub fn open(url: &Url, certs_source: CertsSource) -> CargoResult<Self> {
118        if url.scheme() == "file" {
119            LocalIndex::open(url).map(Self::Local)
120        } else {
121            RemoteIndex::open(url, certs_source).map(Self::Remote)
122        }
123    }
124
125    pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
126        match self {
127            Self::Local(index) => index.krate(name),
128            Self::Remote(index) => index.krate(name),
129        }
130    }
131}
132
133pub struct LocalIndex {
134    index: tame_index::index::LocalRegistry,
135    root: tame_index::PathBuf,
136}
137
138impl LocalIndex {
139    pub fn open(url: &Url) -> CargoResult<Self> {
140        let path = url
141            .to_file_path()
142            .map_err(|()| anyhow::format_err!("invalid local registry {url}"))?;
143        let path = tame_index::PathBuf::from_path_buf(path)
144            .map_err(|_err| anyhow::format_err!("invalid local registry {url:?}"))?;
145        let index = tame_index::index::LocalRegistry::open(path.clone(), false)?;
146        Ok(Self { index, root: path })
147    }
148
149    pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
150        let name = tame_index::KrateName::cargo(name)?;
151        // HACK: for some reason, `tame_index` puts `index` in the middle
152        let entry_path = self.index.krate_path(name);
153        let rel_path = entry_path
154            .strip_prefix(&self.root)
155            .map_err(|_err| anyhow::format_err!("invalid index path {entry_path:?}"))?;
156        let rel_path = rel_path
157            .strip_prefix("index")
158            .map_err(|_err| anyhow::format_err!("invalid index path {entry_path:?}"))?;
159        let entry_path = self.root.join(rel_path);
160        let Ok(entry) = std::fs::read(&entry_path) else {
161            return Ok(None);
162        };
163        let results = IndexKrate::from_slice(&entry)?;
164        Ok(Some(results))
165    }
166}
167
168pub struct RemoteIndex {
169    index: tame_index::SparseIndex,
170    client: tame_index::external::reqwest::blocking::Client,
171    lock: FileLock,
172    etags: Vec<(String, String)>,
173}
174
175impl RemoteIndex {
176    pub fn open(url: &Url, certs_source: CertsSource) -> CargoResult<Self> {
177        let url = url.to_string();
178        let url = tame_index::IndexUrl::NonCratesIo(std::borrow::Cow::Owned(url));
179        let index = tame_index::SparseIndex::new(tame_index::IndexLocation::new(url))?;
180
181        let client = {
182            let builder = tame_index::external::reqwest::blocking::ClientBuilder::new();
183
184            let builder = match certs_source {
185                CertsSource::Webpki => builder.tls_built_in_webpki_certs(true),
186                CertsSource::Native => builder.tls_built_in_native_certs(true),
187            };
188
189            builder.build()?
190        };
191
192        let lock = FileLock::unlocked();
193
194        Ok(Self {
195            index,
196            client,
197            lock,
198            etags: Vec::new(),
199        })
200    }
201
202    pub(crate) fn krate(&mut self, name: &str) -> CargoResult<Option<IndexKrate>> {
203        let etag = self
204            .etags
205            .iter()
206            .find_map(|(krate, etag)| (krate == name).then_some(etag.as_str()))
207            .unwrap_or("");
208
209        let krate_name = name.try_into()?;
210        let req = self
211            .index
212            .make_remote_request(krate_name, Some(etag), &self.lock)?;
213        let (
214            tame_index::external::http::request::Parts {
215                method,
216                uri,
217                version,
218                headers,
219                ..
220            },
221            _,
222        ) = req.into_parts();
223        let mut req = self.client.request(method, uri.to_string());
224        req = req.version(version);
225        req = req.headers(headers);
226        let res = self.client.execute(req.build()?)?;
227
228        // Grab the etag if it exists for future requests
229        if let Some(etag) = res
230            .headers()
231            .get(tame_index::external::reqwest::header::ETAG)
232        {
233            if let Ok(etag) = etag.to_str() {
234                if let Some(i) = self.etags.iter().position(|(krate, _)| krate == name) {
235                    etag.clone_into(&mut self.etags[i].1);
236                } else {
237                    self.etags.push((name.to_owned(), etag.to_owned()));
238                }
239            }
240        }
241
242        let mut builder = tame_index::external::http::Response::builder()
243            .status(res.status())
244            .version(res.version());
245
246        builder
247            .headers_mut()
248            .unwrap()
249            .extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone())));
250
251        let body = res.bytes()?;
252        let response = builder
253            .body(body.to_vec())
254            .map_err(|e| tame_index::Error::from(tame_index::error::HttpError::from(e)))?;
255
256        self.index
257            .parse_remote_response(krate_name, response, false, &self.lock)
258            .map_err(Into::into)
259    }
260}