1use futures_io::AsyncRead;
4use futures_util::future::{BoxFuture, FutureExt as _, TryFutureExt as _};
5use futures_util::stream::TryStreamExt;
6use http::{Response, StatusCode, Uri};
7use http_body_util::BodyExt;
8use hyper_util::client::legacy::connect::Connect;
9use hyper_util::client::legacy::Client;
10type Body = http_body_util::Full<hyper::body::Bytes>;
11use hyper::Request;
12use percent_encoding::utf8_percent_encode;
13use std::future::Future;
14use std::io;
15use std::marker::PhantomData;
16use url::Url;
17
18use crate::error::Error;
19use crate::metadata::{MetadataPath, MetadataVersion, TargetPath};
20use crate::pouf::Pouf;
21use crate::repository::RepositoryProvider;
22use crate::util::SafeAsyncRead;
23use crate::Result;
24
25pub struct HttpRepositoryBuilder<C, D>
27where
28 C: Connect + Sync + 'static,
29 D: Pouf,
30{
31 uri: Uri,
32 client: Client<C, Body>,
33 user_agent: Option<String>,
34 metadata_prefix: Option<Vec<String>>,
35 targets_prefix: Option<Vec<String>>,
36 min_bytes_per_second: u32,
37 _pouf: PhantomData<D>,
38}
39
40impl<C, D> HttpRepositoryBuilder<C, D>
41where
42 C: Connect + Sync + 'static,
43 D: Pouf,
44{
45 pub fn new(url: Url, client: Client<C, Body>) -> Self {
47 HttpRepositoryBuilder {
48 uri: url.to_string().parse::<Uri>().unwrap(), client,
50 user_agent: None,
51 metadata_prefix: None,
52 targets_prefix: None,
53 min_bytes_per_second: 4096,
54 _pouf: PhantomData,
55 }
56 }
57
58 pub fn new_with_uri(uri: Uri, client: Client<C, Body>) -> Self {
60 HttpRepositoryBuilder {
61 uri,
62 client,
63 user_agent: None,
64 metadata_prefix: None,
65 targets_prefix: None,
66 min_bytes_per_second: 4096,
67 _pouf: PhantomData,
68 }
69 }
70
71 pub fn user_agent<T: Into<String>>(mut self, user_agent: T) -> Self {
77 self.user_agent = Some(user_agent.into());
78 self
79 }
80
81 pub fn metadata_prefix(mut self, metadata_prefix: Vec<String>) -> Self {
87 self.metadata_prefix = Some(metadata_prefix);
88 self
89 }
90
91 pub fn targets_prefix(mut self, targets_prefix: Vec<String>) -> Self {
97 self.targets_prefix = Some(targets_prefix);
98 self
99 }
100
101 pub fn min_bytes_per_second(mut self, min: u32) -> Self {
103 self.min_bytes_per_second = min;
104 self
105 }
106
107 pub fn build(self) -> HttpRepository<C, D> {
109 let user_agent = match self.user_agent {
110 Some(user_agent) => user_agent,
111 None => "rust-tuf".into(),
112 };
113
114 HttpRepository {
115 uri: self.uri,
116 client: self.client,
117 user_agent,
118 metadata_prefix: self.metadata_prefix,
119 targets_prefix: self.targets_prefix,
120 min_bytes_per_second: self.min_bytes_per_second,
121 _pouf: PhantomData,
122 }
123 }
124}
125
126#[derive(Debug)]
128pub struct HttpRepository<C, D>
129where
130 C: Connect + Sync + 'static,
131 D: Pouf,
132{
133 uri: Uri,
134 client: Client<C, Body>,
135 user_agent: String,
136 metadata_prefix: Option<Vec<String>>,
137 targets_prefix: Option<Vec<String>>,
138 min_bytes_per_second: u32,
139 _pouf: PhantomData<D>,
140}
141
142const URLENCODE_FRAGMENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
145 .add(b' ')
146 .add(b'"')
147 .add(b'<')
148 .add(b'>')
149 .add(b'`');
150const URLENCODE_PATH: &percent_encoding::AsciiSet =
151 &URLENCODE_FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}');
152
153fn extend_uri(uri: &Uri, prefix: &Option<Vec<String>>, components: &[String]) -> Result<Uri> {
154 let uri = uri.clone();
155 let mut uri_parts = uri.into_parts();
156
157 let (path, query) = match &uri_parts.path_and_query {
158 Some(path_and_query) => (path_and_query.path(), path_and_query.query()),
159 None => ("", None),
160 };
161
162 let mut modified_path = path.to_owned();
163 if modified_path.ends_with('/') {
164 modified_path.pop();
165 }
166
167 let mut path_split = modified_path
168 .split('/')
169 .map(String::from)
170 .collect::<Vec<_>>();
171 let mut new_path_elements: Vec<&str> = vec![];
172
173 if let Some(ref prefix) = prefix {
174 new_path_elements.extend(prefix.iter().map(String::as_str));
175 }
176 new_path_elements.extend(components.iter().map(String::as_str));
177
178 let encoded_new_path_elements = new_path_elements
181 .into_iter()
182 .map(|path_segment| utf8_percent_encode(path_segment, URLENCODE_PATH).collect());
183 path_split.extend(encoded_new_path_elements);
184 let constructed_path = path_split.join("/");
185
186 uri_parts.path_and_query =
187 match query {
188 Some(query) => Some(format!("{}?{}", constructed_path, query).parse().map_err(
189 |_| {
190 Error::IllegalArgument(format!(
191 "Invalid path and query: {:?}, {:?}",
192 constructed_path, query
193 ))
194 },
195 )?),
196 None => Some(constructed_path.parse().map_err(|_| {
197 Error::IllegalArgument(format!("Invalid URI path: {:?}", constructed_path))
198 })?),
199 };
200
201 Uri::from_parts(uri_parts).map_err(|_| {
202 Error::IllegalArgument(format!(
203 "Invalid URI parts: {:?}, {:?}, {:?}",
204 constructed_path, prefix, components
205 ))
206 })
207}
208
209impl<C, D> HttpRepository<C, D>
210where
211 C: Connect + Clone + Send + Sync + 'static,
212 D: Pouf,
213{
214 fn get<'a>(
215 &self,
216 uri: &'a Uri,
217 ) -> Result<impl Future<Output = Result<Response<hyper::body::Incoming>>> + 'a> {
218 let req = Request::builder()
219 .uri(uri)
220 .header("User-Agent", &*self.user_agent)
221 .body(http_body_util::Full::default())
222 .map_err(|err| Error::Http {
223 uri: uri.to_string(),
224 err,
225 })?;
226
227 Ok(self.client.request(req).map_err(|err| Error::Hyper {
228 uri: uri.to_string(),
229 err,
230 }))
231 }
232}
233
234impl<C, D> RepositoryProvider<D> for HttpRepository<C, D>
235where
236 C: Connect + Clone + Send + Sync + 'static,
237 D: Pouf,
238{
239 fn fetch_metadata<'a>(
240 &'a self,
241 meta_path: &MetadataPath,
242 version: MetadataVersion,
243 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
244 let meta_path = meta_path.clone();
245 let components = meta_path.components::<D>(version);
246 let uri = extend_uri(&self.uri, &self.metadata_prefix, &components);
247
248 async move {
249 let uri = uri?;
252 let resp = self.get(&uri)?.await?;
253
254 let status = resp.status();
255 if status == StatusCode::OK {
256 let reader = http_body_util::BodyStream::new(resp.into_body())
257 .map_ok(|frame| frame.into_data().unwrap_or_default())
258 .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
259 .into_async_read()
260 .enforce_minimum_bitrate(self.min_bytes_per_second);
261
262 let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(reader);
263 Ok(reader)
264 } else if status == StatusCode::NOT_FOUND {
265 Err(Error::MetadataNotFound {
266 path: meta_path,
267 version,
268 })
269 } else {
270 Err(Error::BadHttpStatus {
271 uri: uri.to_string(),
272 code: status,
273 })
274 }
275 }
276 .boxed()
277 }
278
279 fn fetch_target<'a>(
280 &'a self,
281 target_path: &TargetPath,
282 ) -> BoxFuture<'a, Result<Box<dyn AsyncRead + Send + Unpin + 'a>>> {
283 let target_path = target_path.clone();
284 let components = target_path.components();
285 let uri = extend_uri(&self.uri, &self.targets_prefix, &components);
286
287 async move {
288 let uri = uri?;
291 let resp = self.get(&uri)?.await?;
292
293 let status = resp.status();
294 if status == StatusCode::OK {
295 let reader = http_body_util::BodyStream::new(resp.into_body())
296 .map_ok(|frame| frame.into_data().unwrap_or_default())
297 .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
298 .into_async_read()
299 .enforce_minimum_bitrate(self.min_bytes_per_second);
300
301 let reader: Box<dyn AsyncRead + Send + Unpin> = Box::new(reader);
302 Ok(reader)
303 } else if status == StatusCode::NOT_FOUND {
304 Err(Error::TargetNotFound(target_path))
305 } else {
306 Err(Error::BadHttpStatus {
307 uri: uri.to_string(),
308 code: status,
309 })
310 }
311 }
312 .boxed()
313 }
314}
315
316#[cfg(test)]
317mod test {
318 use super::*;
319
320 fn http_repository_extend_using_url(
323 base_url: Url,
324 prefix: &Option<Vec<String>>,
325 components: &[String],
326 ) -> url::Url {
327 let mut url = base_url;
328 {
329 let mut segments = url.path_segments_mut().unwrap();
330 if let Some(ref prefix) = prefix {
331 segments.extend(prefix);
332 }
333 segments.extend(components);
334 }
335 url
336 }
337
338 #[test]
339 fn http_repository_uri_construction() {
340 let base_uri = "http://example.com/one";
341
342 let prefix = Some(vec![String::from("prefix")]);
343 let components = [
344 String::from("components_one"),
345 String::from("components_two"),
346 ];
347
348 let uri = base_uri.parse::<Uri>().unwrap();
349 let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
350
351 let url =
352 http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
353
354 assert_eq!(url.to_string(), extended_uri.to_string());
355 assert_eq!(
356 extended_uri.to_string(),
357 "http://example.com/one/prefix/components_one/components_two"
358 );
359 }
360
361 #[test]
362 fn http_repository_uri_construction_encoded() {
363 let base_uri = "http://example.com/one";
364
365 let prefix = Some(vec![String::from("prefix")]);
366 let components = [String::from("chars to encode#?")];
367 let uri = base_uri.parse::<Uri>().unwrap();
368 let extended_uri = extend_uri(&uri, &prefix, &components)
369 .expect("correctly generated a URI with a zone id");
370
371 let url =
372 http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
373
374 assert_eq!(url.to_string(), extended_uri.to_string());
375 assert_eq!(
376 extended_uri.to_string(),
377 "http://example.com/one/prefix/chars%20to%20encode%23%3F"
378 );
379 }
380
381 #[test]
382 fn http_repository_uri_construction_no_components() {
383 let base_uri = "http://example.com/one";
384
385 let prefix = Some(vec![String::from("prefix")]);
386 let components = [];
387
388 let uri = base_uri.parse::<Uri>().unwrap();
389 let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
390
391 let url =
392 http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
393
394 assert_eq!(url.to_string(), extended_uri.to_string());
395 assert_eq!(extended_uri.to_string(), "http://example.com/one/prefix");
396 }
397
398 #[test]
399 fn http_repository_uri_construction_no_prefix() {
400 let base_uri = "http://example.com/one";
401
402 let prefix = None;
403 let components = [
404 String::from("components_one"),
405 String::from("components_two"),
406 ];
407
408 let uri = base_uri.parse::<Uri>().unwrap();
409 let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
410
411 let url =
412 http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
413
414 assert_eq!(url.to_string(), extended_uri.to_string());
415 assert_eq!(
416 extended_uri.to_string(),
417 "http://example.com/one/components_one/components_two"
418 );
419 }
420
421 #[test]
422 fn http_repository_uri_construction_with_query() {
423 let base_uri = "http://example.com/one?test=1";
424
425 let prefix = None;
426 let components = [
427 String::from("components_one"),
428 String::from("components_two"),
429 ];
430
431 let uri = base_uri.parse::<Uri>().unwrap();
432 let extended_uri = extend_uri(&uri, &prefix, &components).unwrap();
433
434 let url =
435 http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components);
436
437 assert_eq!(url.to_string(), extended_uri.to_string());
438 assert_eq!(
439 extended_uri.to_string(),
440 "http://example.com/one/components_one/components_two?test=1"
441 );
442 }
443
444 #[test]
445 fn http_repository_uri_construction_ipv6_zoneid() {
446 let base_uri = "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80";
447
448 let prefix = Some(vec![String::from("prefix")]);
449 let components = [
450 String::from("componenents_one"),
451 String::from("components_two"),
452 ];
453 let uri = base_uri.parse::<Uri>().unwrap();
454 let extended_uri = extend_uri(&uri, &prefix, &components)
455 .expect("correctly generated a URI with a zone id");
456 assert_eq!(
457 extended_uri.to_string(),
458 "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80/prefix/componenents_one/components_two"
459 );
460 }
461}