From 4a4ca90dcdd100d53a17bc4ddc0b6325881e6a45 Mon Sep 17 00:00:00 2001 From: John Wittrock Date: Tue, 10 Dec 2019 01:10:43 -0500 Subject: [PATCH 1/3] Enable IPv6 zone ID support for HttpRepository Accomplished by switching to `http::Uri` rather than `url::Url`. This is somewhat unfortunate, as the ergonomics of `http::Uri` are notably worse, but the result is that zone IDs work. For API compatibility purposes, the changes manifest as a new struct called HttpRepository2 and HttpRepositoryBuilder2. In a subsequent commit we can delete HttpRepository and rename HttpRepository2. --- Cargo.toml | 1 + src/client.rs | 6 +- src/repository.rs | 461 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 453 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index edebeca4..d32af67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ itoa = "0.4" log = "0.4" ring = { version = "0.16" } parking_lot = "0.9" +percent-encoding = "2.1" serde = "1" serde_derive = "1" serde_json = "1" diff --git a/src/client.rs b/src/client.rs index 460b8166..1592fafd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,7 +13,7 @@ //! # use tuf::metadata::{RootMetadata, SignedMetadata, Role, MetadataPath, //! # MetadataVersion}; //! # use tuf::interchange::Json; -//! # use tuf::repository::{Repository, FileSystemRepository, HttpRepositoryBuilder}; +//! # use tuf::repository::{Repository, FileSystemRepository, HttpRepositoryBuilder2}; //! //! static TRUSTED_ROOT_KEY_IDS: &'static [&str] = &[ //! "4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db", @@ -29,8 +29,8 @@ //! //! let local = FileSystemRepository::::new(PathBuf::from("~/.rustup"))?; //! -//! let remote = HttpRepositoryBuilder::new( -//! url::Url::parse("https://static.rust-lang.org/").unwrap(), +//! let remote = HttpRepositoryBuilder2::new( +//! "https://static.rust-lang.org/".parse::().unwrap(), //! HttpClient::new(), //! ) //! .user_agent("rustup/1.4.0") diff --git a/src/repository.rs b/src/repository.rs index 56dadd8e..cee20e27 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -12,6 +12,7 @@ use hyper::Client; use hyper::Request; use log::debug; use parking_lot::RwLock; +use percent_encoding::utf8_percent_encode; use std::collections::HashMap; use std::fs::{DirBuilder, File}; use std::io; @@ -19,6 +20,7 @@ use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::sync::Arc; use tempfile::{self, NamedTempFile}; +use url::Url; use crate::crypto::{self, HashAlgorithm, HashValue}; use crate::error::Error; @@ -28,7 +30,6 @@ use crate::metadata::{ }; use crate::util::SafeReader; use crate::Result; -use url::Url; /// Top-level trait that represents a TUF repository and contains all the ways it can be interacted /// with. @@ -264,7 +265,7 @@ where Ok(()) } - .boxed() + .boxed() } /// Fetch signed metadata. @@ -296,7 +297,7 @@ where Ok(D::from_slice(&buf)?) } - .boxed() + .boxed() } fn store_target<'a, R>( @@ -321,7 +322,7 @@ where Ok(()) } - .boxed() + .boxed() } fn fetch_target<'a>( @@ -348,7 +349,7 @@ where Ok(reader) } - .boxed() + .boxed() } } @@ -537,7 +538,318 @@ where "Http repo store metadata not implemented".to_string(), )) } - .boxed() + .boxed() + } + + fn fetch_metadata<'a, M>( + &'a self, + meta_path: &'a MetadataPath, + version: &'a MetadataVersion, + max_length: Option, + hash_data: Option<(&'static HashAlgorithm, HashValue)>, + ) -> BoxFuture<'a, Result>> + where + M: Metadata + 'static, + { + async move { + Self::check::(meta_path)?; + + let components = meta_path.components::(&version); + let resp = self.get(&self.metadata_prefix, &components).await?; + + let stream = resp + .into_body() + .compat() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); + + let mut reader = SafeReader::new( + stream.into_async_read(), + max_length.unwrap_or(::std::usize::MAX) as u64, + self.min_bytes_per_second, + hash_data, + )?; + + let mut buf = Vec::new(); + reader.read_to_end(&mut buf).await?; + + Ok(D::from_slice(&buf)?) + } + .boxed() + } + + /// This always returns `Err` as storing over HTTP is not yet supported. + fn store_target<'a, R>(&'a self, _: R, _: &'a TargetPath) -> BoxFuture<'a, Result<()>> + where + R: AsyncRead + 'a, + { + async { Err(Error::Opaque("Http repo store not implemented".to_string())) }.boxed() + } + + fn fetch_target<'a>( + &'a self, + target_path: &'a TargetPath, + target_description: &'a TargetDescription, + ) -> BoxFuture<'a, Result>> { + async move { + let (alg, value) = crypto::hash_preference(target_description.hashes())?; + let components = target_path.components(); + let resp = self.get(&self.targets_prefix, &components).await?; + + let stream = resp + .into_body() + .compat() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); + + let reader = SafeReader::new( + stream.into_async_read(), + target_description.length(), + self.min_bytes_per_second, + Some((alg, value.clone())), + )?; + + Ok(Box::new(reader) as Box) + } + .boxed() + } +} + +/// A builder to create a repository accessible over HTTP. +pub struct HttpRepositoryBuilder2 +where + C: Connect + Sync + 'static, + D: DataInterchange, +{ + uri: Uri, + client: Client, + interchange: PhantomData, + user_agent: Option, + metadata_prefix: Option>, + targets_prefix: Option>, + min_bytes_per_second: u32, +} + +impl HttpRepositoryBuilder2 +where + C: Connect + Sync + 'static, + D: DataInterchange, +{ + /// Create a new repository with the given `Uri` and `Client`. + pub fn new(uri: Uri, client: Client) -> Self { + HttpRepositoryBuilder2 { + uri: uri, + client: client, + interchange: PhantomData, + user_agent: None, + metadata_prefix: None, + targets_prefix: None, + min_bytes_per_second: 4096, + } + } + + /// Set the User-Agent prefix. + /// + /// Callers *should* include a custom User-Agent prefix to help maintainers of TUF repositories + /// keep track of which client versions exist in the field. + /// + pub fn user_agent>(mut self, user_agent: T) -> Self { + self.user_agent = Some(user_agent.into()); + self + } + + /// The argument `metadata_prefix` is used to provide an alternate path where metadata is + /// stored on the repository. If `None`, this defaults to `/`. For example, if there is a TUF + /// repository at `https://tuf.example.com/`, but all metadata is stored at `/meta/`, then + /// passing the arg `Some("meta".into())` would cause `root.json` to be fetched from + /// `https://tuf.example.com/meta/root.json`. + pub fn metadata_prefix(mut self, metadata_prefix: Vec) -> Self { + self.metadata_prefix = Some(metadata_prefix); + self + } + + /// The argument `targets_prefix` is used to provide an alternate path where targets is + /// stored on the repository. If `None`, this defaults to `/`. For example, if there is a TUF + /// repository at `https://tuf.example.com/`, but all targets are stored at `/targets/`, then + /// passing the arg `Some("targets".into())` would cause `hello-world` to be fetched from + /// `https://tuf.example.com/targets/hello-world`. + pub fn targets_prefix(mut self, targets_prefix: Vec) -> Self { + self.targets_prefix = Some(targets_prefix); + self + } + + /// Set the minimum bytes per second for a read to be considered good. + pub fn min_bytes_per_second(mut self, min: u32) -> Self { + self.min_bytes_per_second = min; + self + } + + /// Build a `HttpRepository2`. + pub fn build(self) -> HttpRepository2 { + let user_agent = match self.user_agent { + Some(user_agent) => user_agent, + None => "rust-tuf".into(), + }; + + HttpRepository2 { + uri: self.uri, + client: self.client, + interchange: self.interchange, + user_agent: user_agent, + metadata_prefix: self.metadata_prefix, + targets_prefix: self.targets_prefix, + min_bytes_per_second: self.min_bytes_per_second, + } + } +} + +/// A repository accessible over HTTP. +pub struct HttpRepository2 +where + C: Connect + Sync + 'static, + D: DataInterchange, +{ + uri: Uri, + client: Client, + user_agent: String, + metadata_prefix: Option>, + targets_prefix: Option>, + min_bytes_per_second: u32, + interchange: PhantomData, +} + +// Configuration for urlencoding URI path elements. +// From https://url.spec.whatwg.org/#path-percent-encode-set +const URLENCODE_FRAGMENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS + .add(b' ') + .add(b'"') + .add(b'<') + .add(b'>') + .add(b'`'); +const URLENCODE_PATH: &percent_encoding::AsciiSet = &URLENCODE_FRAGMENT + .add(b'/') + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + +fn extend_uri(uri: Uri, prefix: &Option>, components: &[String]) -> Result { + let mut uri_parts = uri.into_parts(); + + let (path, query) = match &uri_parts.path_and_query { + Some(path_and_query) => (path_and_query.path(), path_and_query.query()), + None => ("", None), + }; + + let mut modified_path = path.to_owned(); + if modified_path.ends_with("/") { + modified_path.pop(); + } + + let mut path_split = modified_path + .split("/") + .map(String::from) + .collect::>(); + let mut new_path_elements: Vec = vec![]; + + if let Some(ref prefix) = prefix { + new_path_elements.extend(prefix.iter().cloned()); + } + new_path_elements.append(&mut components.to_vec()); + + // Urlencode new items to match behavior of PathSegmentsMut.extend from + // https://docs.rs/url/2.1.0/url/struct.PathSegmentsMut.html + let encoded_new_path_elements: Vec = new_path_elements + .into_iter() + .map(|path_segment| utf8_percent_encode(&path_segment, URLENCODE_PATH).collect()) + .collect(); + + path_split.extend(encoded_new_path_elements); + let constructed_path = path_split.join("/"); + + uri_parts.path_and_query = + match query { + Some(query) => Some(format!("{}?{}", constructed_path, query).parse().map_err( + |_| { + Error::IllegalArgument(format!( + "Invalid path and query: {:?}, {:?}", + constructed_path, query + )) + }, + )?), + None => Some(constructed_path.parse().map_err(|_| { + Error::IllegalArgument(format!("Invalid URI path: {:?}", constructed_path)) + })?), + }; + + Ok(Uri::from_parts(uri_parts).map_err(|_| { + Error::IllegalArgument(format!( + "Invalid URI parts: {:?}, {:?}, {:?}", + constructed_path, prefix, components + )) + })?) +} + +impl HttpRepository2 +where + C: Connect + Sync + 'static, + D: DataInterchange, +{ + async fn get<'a>( + &'a self, + prefix: &'a Option>, + components: &'a [String], + ) -> Result> { + let base_uri = self.uri.clone(); + let uri = extend_uri(base_uri, prefix, components)?; + + let req = Request::builder() + .uri(uri) + .header("User-Agent", &*self.user_agent) + .body(Body::default())?; + + let resp = self.client.request(req).compat().await?; + let status = resp.status(); + + if !status.is_success() { + if status == StatusCode::NOT_FOUND { + Err(Error::NotFound) + } else { + Err(Error::Opaque(format!( + "Error getting {:?}: {:?}", + self.uri, resp + ))) + } + } else { + Ok(resp) + } + } +} + +impl Repository for HttpRepository2 +where + C: Connect + Sync + 'static, + D: DataInterchange + Send + Sync, +{ + /// This always returns `Err` as storing over HTTP is not yet supported. + fn store_metadata<'a, M>( + &'a self, + _: &'a MetadataPath, + _: &'a MetadataVersion, + _: &'a SignedMetadata, + ) -> BoxFuture<'a, Result<()>> + where + M: Metadata + 'static, + { + async { + Err(Error::Opaque( + "Http repo store metadata not implemented".to_string(), + )) + } + .boxed() } fn fetch_metadata<'a, M>( @@ -573,7 +885,7 @@ where Ok(D::from_slice(&buf)?) } - .boxed() + .boxed() } /// This always returns `Err` as storing over HTTP is not yet supported. @@ -608,7 +920,7 @@ where Ok(Box::new(reader) as Box) } - .boxed() + .boxed() } } @@ -670,7 +982,7 @@ where .insert((meta_path.clone(), version.clone()), buf); Ok(()) } - .boxed() + .boxed() } fn fetch_metadata<'a, M>( @@ -709,7 +1021,7 @@ where D::from_slice(&buf) } - .boxed() + .boxed() } fn store_target<'a, R>( @@ -728,7 +1040,7 @@ where .insert(target_path.clone(), Arc::new(buf)); Ok(()) } - .boxed() + .boxed() } fn fetch_target<'a>( @@ -763,7 +1075,7 @@ where Ok(reader) } - .boxed() + .boxed() } } @@ -774,6 +1086,131 @@ mod test { use futures_executor::block_on; use tempfile; + // Old behavior of the `HttpRepository::get` extension + // functionality + fn http_repository_extend_using_url( + base_url: Url, + prefix: &Option>, + components: &[String], + ) -> url::Url { + let mut url = base_url.clone(); + { + let mut segments = url.path_segments_mut().unwrap(); + if let Some(ref prefix) = prefix { + segments.extend(prefix); + } + segments.extend(components); + } + return url; + } + + #[test] + fn http_repository2_uri_construction() { + let base_uri = "http://example.com/one"; + + let prefix = Some(vec![String::from("prefix")]); + let components = [ + String::from("componenents_one"), + String::from("components_two"), + ]; + + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); + } + + #[test] + fn http_repository2_uri_construction_encoded() { + let base_uri = "http://example.com/one"; + + let prefix = Some(vec![String::from("prefix")]); + let components = [String::from("chars_to_encode<>!")]; + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components) + .expect("correctly generated a URI with a zone id"); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); + } + + #[test] + fn http_repository2_uri_construction_no_components() { + let base_uri = "http://example.com/one"; + + let prefix = Some(vec![String::from("prefix")]); + let components = []; + + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); + } + + #[test] + fn http_repository2_uri_construction_ipv6_zoneid() { + let base_uri = "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80"; + + let prefix = Some(vec![String::from("prefix")]); + let components = [ + String::from("componenents_one"), + String::from("components_two"), + ]; + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components) + .expect("correctly generated a URI with a zone id"); + assert_eq!( + extended_uri.to_string(), + "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80/prefix/componenents_one/components_two" + ); + } + + #[test] + fn http_repository2_uri_construction_no_prefix() { + let base_uri = "http://example.com/one"; + + let prefix = None; + let components = [ + String::from("componenents_one"), + String::from("illegal_characters<>"), + ]; + + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); + } + + #[test] + fn http_repository2_uri_construction_with_query() { + let base_uri = "http://example.com/one?test=1"; + + let prefix = None; + let components = [ + String::from("componenents_one"), + String::from("illegal_characters<>"), + ]; + + let uri = base_uri.parse::().unwrap(); + let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); + } + #[test] fn ephemeral_repo_targets() { block_on(async { From 2ee9f037ba1183e33b53e469933a3adeac6dfc47 Mon Sep 17 00:00:00 2001 From: John Wittrock Date: Tue, 17 Dec 2019 11:15:52 -0500 Subject: [PATCH 2/3] Review comments. --- src/client.rs | 4 +- src/repository.rs | 369 +++++++++------------------------------------- 2 files changed, 73 insertions(+), 300 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1592fafd..fe3a11f3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,7 +13,7 @@ //! # use tuf::metadata::{RootMetadata, SignedMetadata, Role, MetadataPath, //! # MetadataVersion}; //! # use tuf::interchange::Json; -//! # use tuf::repository::{Repository, FileSystemRepository, HttpRepositoryBuilder2}; +//! # use tuf::repository::{Repository, FileSystemRepository, HttpRepositoryBuilder}; //! //! static TRUSTED_ROOT_KEY_IDS: &'static [&str] = &[ //! "4750eaf6878740780d6f97b12dbad079fb012bec88c78de2c380add56d3f51db", @@ -29,7 +29,7 @@ //! //! let local = FileSystemRepository::::new(PathBuf::from("~/.rustup"))?; //! -//! let remote = HttpRepositoryBuilder2::new( +//! let remote = HttpRepositoryBuilder::new_with_uri( //! "https://static.rust-lang.org/".parse::().unwrap(), //! HttpClient::new(), //! ) diff --git a/src/repository.rs b/src/repository.rs index cee20e27..f73f233b 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -265,7 +265,7 @@ where Ok(()) } - .boxed() + .boxed() } /// Fetch signed metadata. @@ -297,7 +297,7 @@ where Ok(D::from_slice(&buf)?) } - .boxed() + .boxed() } fn store_target<'a, R>( @@ -322,7 +322,7 @@ where Ok(()) } - .boxed() + .boxed() } fn fetch_target<'a>( @@ -349,7 +349,7 @@ where Ok(reader) } - .boxed() + .boxed() } } @@ -373,7 +373,7 @@ where C: Connect + Sync + 'static, D: DataInterchange, { - url: Url, + uri: Uri, client: Client, interchange: PhantomData, user_agent: Option, @@ -390,7 +390,7 @@ where /// Create a new repository with the given `Url` and `Client`. pub fn new(url: Url, client: Client) -> Self { HttpRepositoryBuilder { - url: url, + uri: url.to_string().parse::().unwrap(), // This is dangerous, but will only exist for a short time as we migrate APIs. client: client, interchange: PhantomData, user_agent: None, @@ -400,242 +400,9 @@ where } } - /// Set the User-Agent prefix. - /// - /// Callers *should* include a custom User-Agent prefix to help maintainers of TUF repositories - /// keep track of which client versions exist in the field. - /// - pub fn user_agent>(mut self, user_agent: T) -> Self { - self.user_agent = Some(user_agent.into()); - self - } - - /// The argument `metadata_prefix` is used to provide an alternate path where metadata is - /// stored on the repository. If `None`, this defaults to `/`. For example, if there is a TUF - /// repository at `https://tuf.example.com/`, but all metadata is stored at `/meta/`, then - /// passing the arg `Some("meta".into())` would cause `root.json` to be fetched from - /// `https://tuf.example.com/meta/root.json`. - pub fn metadata_prefix(mut self, metadata_prefix: Vec) -> Self { - self.metadata_prefix = Some(metadata_prefix); - self - } - - /// The argument `targets_prefix` is used to provide an alternate path where targets is - /// stored on the repository. If `None`, this defaults to `/`. For example, if there is a TUF - /// repository at `https://tuf.example.com/`, but all targets are stored at `/targets/`, then - /// passing the arg `Some("targets".into())` would cause `hello-world` to be fetched from - /// `https://tuf.example.com/targets/hello-world`. - pub fn targets_prefix(mut self, targets_prefix: Vec) -> Self { - self.targets_prefix = Some(targets_prefix); - self - } - - /// Set the minimum bytes per second for a read to be considered good. - pub fn min_bytes_per_second(mut self, min: u32) -> Self { - self.min_bytes_per_second = min; - self - } - - /// Build a `HttpRepository`. - pub fn build(self) -> HttpRepository { - let user_agent = match self.user_agent { - Some(user_agent) => user_agent, - None => "rust-tuf".into(), - }; - - HttpRepository { - url: self.url, - client: self.client, - interchange: self.interchange, - user_agent: user_agent, - metadata_prefix: self.metadata_prefix, - targets_prefix: self.targets_prefix, - min_bytes_per_second: self.min_bytes_per_second, - } - } -} - -/// A repository accessible over HTTP. -pub struct HttpRepository -where - C: Connect + Sync + 'static, - D: DataInterchange, -{ - url: Url, - client: Client, - user_agent: String, - metadata_prefix: Option>, - targets_prefix: Option>, - min_bytes_per_second: u32, - interchange: PhantomData, -} - -impl HttpRepository -where - C: Connect + Sync + 'static, - D: DataInterchange, -{ - async fn get<'a>( - &'a self, - prefix: &'a Option>, - components: &'a [String], - ) -> Result> { - let mut url = self.url.clone(); - { - let mut segments = url.path_segments_mut().map_err(|_| { - Error::IllegalArgument(format!("URL was 'cannot-be-a-base': {:?}", self.url)) - })?; - if let Some(ref prefix) = prefix { - segments.extend(prefix); - } - segments.extend(components); - } - - let uri: Uri = url.into_string().parse().map_err(|_| { - Error::IllegalArgument(format!("URL was 'cannot-be-a-base': {:?}", self.url)) - })?; - - let req = Request::builder() - .uri(uri) - .header("User-Agent", &*self.user_agent) - .body(Body::default())?; - - let resp = self.client.request(req).compat().await?; - let status = resp.status(); - - if !status.is_success() { - if status == StatusCode::NOT_FOUND { - Err(Error::NotFound) - } else { - Err(Error::Opaque(format!( - "Error getting {:?}: {:?}", - self.url, resp - ))) - } - } else { - Ok(resp) - } - } -} - -impl Repository for HttpRepository -where - C: Connect + Sync + 'static, - D: DataInterchange + Send + Sync, -{ - /// This always returns `Err` as storing over HTTP is not yet supported. - fn store_metadata<'a, M>( - &'a self, - _: &'a MetadataPath, - _: &'a MetadataVersion, - _: &'a SignedMetadata, - ) -> BoxFuture<'a, Result<()>> - where - M: Metadata + 'static, - { - async { - Err(Error::Opaque( - "Http repo store metadata not implemented".to_string(), - )) - } - .boxed() - } - - fn fetch_metadata<'a, M>( - &'a self, - meta_path: &'a MetadataPath, - version: &'a MetadataVersion, - max_length: Option, - hash_data: Option<(&'static HashAlgorithm, HashValue)>, - ) -> BoxFuture<'a, Result>> - where - M: Metadata + 'static, - { - async move { - Self::check::(meta_path)?; - - let components = meta_path.components::(&version); - let resp = self.get(&self.metadata_prefix, &components).await?; - - let stream = resp - .into_body() - .compat() - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); - - let mut reader = SafeReader::new( - stream.into_async_read(), - max_length.unwrap_or(::std::usize::MAX) as u64, - self.min_bytes_per_second, - hash_data, - )?; - - let mut buf = Vec::new(); - reader.read_to_end(&mut buf).await?; - - Ok(D::from_slice(&buf)?) - } - .boxed() - } - - /// This always returns `Err` as storing over HTTP is not yet supported. - fn store_target<'a, R>(&'a self, _: R, _: &'a TargetPath) -> BoxFuture<'a, Result<()>> - where - R: AsyncRead + 'a, - { - async { Err(Error::Opaque("Http repo store not implemented".to_string())) }.boxed() - } - - fn fetch_target<'a>( - &'a self, - target_path: &'a TargetPath, - target_description: &'a TargetDescription, - ) -> BoxFuture<'a, Result>> { - async move { - let (alg, value) = crypto::hash_preference(target_description.hashes())?; - let components = target_path.components(); - let resp = self.get(&self.targets_prefix, &components).await?; - - let stream = resp - .into_body() - .compat() - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); - - let reader = SafeReader::new( - stream.into_async_read(), - target_description.length(), - self.min_bytes_per_second, - Some((alg, value.clone())), - )?; - - Ok(Box::new(reader) as Box) - } - .boxed() - } -} - -/// A builder to create a repository accessible over HTTP. -pub struct HttpRepositoryBuilder2 -where - C: Connect + Sync + 'static, - D: DataInterchange, -{ - uri: Uri, - client: Client, - interchange: PhantomData, - user_agent: Option, - metadata_prefix: Option>, - targets_prefix: Option>, - min_bytes_per_second: u32, -} - -impl HttpRepositoryBuilder2 -where - C: Connect + Sync + 'static, - D: DataInterchange, -{ - /// Create a new repository with the given `Uri` and `Client`. - pub fn new(uri: Uri, client: Client) -> Self { - HttpRepositoryBuilder2 { + /// Create a new repository with the given `Url` and `Client`. + pub fn new_with_uri(uri: Uri, client: Client) -> Self { + HttpRepositoryBuilder { uri: uri, client: client, interchange: PhantomData, @@ -682,14 +449,14 @@ where self } - /// Build a `HttpRepository2`. - pub fn build(self) -> HttpRepository2 { + /// Build a `HttpRepository`. + pub fn build(self) -> HttpRepository { let user_agent = match self.user_agent { Some(user_agent) => user_agent, None => "rust-tuf".into(), }; - HttpRepository2 { + HttpRepository { uri: self.uri, client: self.client, interchange: self.interchange, @@ -702,7 +469,7 @@ where } /// A repository accessible over HTTP. -pub struct HttpRepository2 +pub struct HttpRepository where C: Connect + Sync + 'static, D: DataInterchange, @@ -724,17 +491,8 @@ const URLENCODE_FRAGMENT: &percent_encoding::AsciiSet = &percent_encoding::CONTR .add(b'<') .add(b'>') .add(b'`'); -const URLENCODE_PATH: &percent_encoding::AsciiSet = &URLENCODE_FRAGMENT - .add(b'/') - .add(b':') - .add(b';') - .add(b'=') - .add(b'@') - .add(b'[') - .add(b'\\') - .add(b']') - .add(b'^') - .add(b'|'); +const URLENCODE_PATH: &percent_encoding::AsciiSet = + &URLENCODE_FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); fn extend_uri(uri: Uri, prefix: &Option>, components: &[String]) -> Result { let mut uri_parts = uri.into_parts(); @@ -758,15 +516,13 @@ fn extend_uri(uri: Uri, prefix: &Option>, components: &[String]) -> if let Some(ref prefix) = prefix { new_path_elements.extend(prefix.iter().cloned()); } - new_path_elements.append(&mut components.to_vec()); + new_path_elements.extend_from_slice(components); // Urlencode new items to match behavior of PathSegmentsMut.extend from // https://docs.rs/url/2.1.0/url/struct.PathSegmentsMut.html - let encoded_new_path_elements: Vec = new_path_elements + let encoded_new_path_elements = new_path_elements .into_iter() - .map(|path_segment| utf8_percent_encode(&path_segment, URLENCODE_PATH).collect()) - .collect(); - + .map(|path_segment| utf8_percent_encode(&path_segment, URLENCODE_PATH).collect()); path_split.extend(encoded_new_path_elements); let constructed_path = path_split.join("/"); @@ -793,7 +549,7 @@ fn extend_uri(uri: Uri, prefix: &Option>, components: &[String]) -> })?) } -impl HttpRepository2 +impl HttpRepository where C: Connect + Sync + 'static, D: DataInterchange, @@ -829,7 +585,7 @@ where } } -impl Repository for HttpRepository2 +impl Repository for HttpRepository where C: Connect + Sync + 'static, D: DataInterchange + Send + Sync, @@ -849,7 +605,7 @@ where "Http repo store metadata not implemented".to_string(), )) } - .boxed() + .boxed() } fn fetch_metadata<'a, M>( @@ -885,7 +641,7 @@ where Ok(D::from_slice(&buf)?) } - .boxed() + .boxed() } /// This always returns `Err` as storing over HTTP is not yet supported. @@ -920,7 +676,7 @@ where Ok(Box::new(reader) as Box) } - .boxed() + .boxed() } } @@ -982,7 +738,7 @@ where .insert((meta_path.clone(), version.clone()), buf); Ok(()) } - .boxed() + .boxed() } fn fetch_metadata<'a, M>( @@ -1021,7 +777,7 @@ where D::from_slice(&buf) } - .boxed() + .boxed() } fn store_target<'a, R>( @@ -1040,7 +796,7 @@ where .insert(target_path.clone(), Arc::new(buf)); Ok(()) } - .boxed() + .boxed() } fn fetch_target<'a>( @@ -1075,7 +831,7 @@ where Ok(reader) } - .boxed() + .boxed() } } @@ -1105,12 +861,12 @@ mod test { } #[test] - fn http_repository2_uri_construction() { + fn http_repository_uri_construction() { let base_uri = "http://example.com/one"; let prefix = Some(vec![String::from("prefix")]); let components = [ - String::from("componenents_one"), + String::from("components_one"), String::from("components_two"), ]; @@ -1121,14 +877,18 @@ mod test { http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); assert_eq!(url.to_string(), extended_uri.to_string()); + assert_eq!( + extended_uri.to_string(), + "http://example.com/one/prefix/components_one/components_two" + ); } #[test] - fn http_repository2_uri_construction_encoded() { + fn http_repository_uri_construction_encoded() { let base_uri = "http://example.com/one"; let prefix = Some(vec![String::from("prefix")]); - let components = [String::from("chars_to_encode<>!")]; + let components = [String::from("chars to encode#?")]; let uri = base_uri.parse::().unwrap(); let extended_uri = extend_uri(uri, &prefix, &components) .expect("correctly generated a URI with a zone id"); @@ -1137,10 +897,14 @@ mod test { http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); assert_eq!(url.to_string(), extended_uri.to_string()); + assert_eq!( + extended_uri.to_string(), + "http://example.com/one/prefix/chars%20to%20encode%23%3F" + ); } #[test] - fn http_repository2_uri_construction_no_components() { + fn http_repository_uri_construction_no_components() { let base_uri = "http://example.com/one"; let prefix = Some(vec![String::from("prefix")]); @@ -1153,34 +917,40 @@ mod test { http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); assert_eq!(url.to_string(), extended_uri.to_string()); + assert_eq!(extended_uri.to_string(), "http://example.com/one/prefix"); } #[test] - fn http_repository2_uri_construction_ipv6_zoneid() { - let base_uri = "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80"; + fn http_repository_uri_construction_no_prefix() { + let base_uri = "http://example.com/one"; - let prefix = Some(vec![String::from("prefix")]); + let prefix = None; let components = [ - String::from("componenents_one"), + String::from("components_one"), String::from("components_two"), ]; + let uri = base_uri.parse::().unwrap(); - let extended_uri = extend_uri(uri, &prefix, &components) - .expect("correctly generated a URI with a zone id"); + let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); + + let url = + http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); + + assert_eq!(url.to_string(), extended_uri.to_string()); assert_eq!( extended_uri.to_string(), - "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80/prefix/componenents_one/components_two" + "http://example.com/one/components_one/components_two" ); } #[test] - fn http_repository2_uri_construction_no_prefix() { - let base_uri = "http://example.com/one"; + fn http_repository_uri_construction_with_query() { + let base_uri = "http://example.com/one?test=1"; let prefix = None; let components = [ - String::from("componenents_one"), - String::from("illegal_characters<>"), + String::from("components_one"), + String::from("components_two"), ]; let uri = base_uri.parse::().unwrap(); @@ -1190,25 +960,28 @@ mod test { http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); assert_eq!(url.to_string(), extended_uri.to_string()); + assert_eq!( + extended_uri.to_string(), + "http://example.com/one/components_one/components_two?test=1" + ); } #[test] - fn http_repository2_uri_construction_with_query() { - let base_uri = "http://example.com/one?test=1"; + fn http_repository_uri_construction_ipv6_zoneid() { + let base_uri = "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80"; - let prefix = None; + let prefix = Some(vec![String::from("prefix")]); let components = [ String::from("componenents_one"), - String::from("illegal_characters<>"), + String::from("components_two"), ]; - let uri = base_uri.parse::().unwrap(); - let extended_uri = extend_uri(uri, &prefix, &components).unwrap(); - - let url = - http_repository_extend_using_url(Url::parse(base_uri).unwrap(), &prefix, &components); - - assert_eq!(url.to_string(), extended_uri.to_string()); + let extended_uri = extend_uri(uri, &prefix, &components) + .expect("correctly generated a URI with a zone id"); + assert_eq!( + extended_uri.to_string(), + "http://[aaaa::aaaa:aaaa:aaaa:1234%252]:80/prefix/componenents_one/components_two" + ); } #[test] From 566e60ab50094dea320cc57ad1b97466b0ccf09c Mon Sep 17 00:00:00 2001 From: John Wittrock Date: Wed, 18 Dec 2019 10:07:48 -0500 Subject: [PATCH 3/3] Use &str, not String. --- src/repository.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repository.rs b/src/repository.rs index f73f233b..f99ff4d1 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -511,12 +511,12 @@ fn extend_uri(uri: Uri, prefix: &Option>, components: &[String]) -> .split("/") .map(String::from) .collect::>(); - let mut new_path_elements: Vec = vec![]; + let mut new_path_elements: Vec<&str> = vec![]; if let Some(ref prefix) = prefix { - new_path_elements.extend(prefix.iter().cloned()); + new_path_elements.extend(prefix.iter().map(String::as_str)); } - new_path_elements.extend_from_slice(components); + new_path_elements.extend(components.iter().map(String::as_str)); // Urlencode new items to match behavior of PathSegmentsMut.extend from // https://docs.rs/url/2.1.0/url/struct.PathSegmentsMut.html