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