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