Merge branch 'develop'
diff --git a/Cargo.toml b/Cargo.toml
index c8a21ce..45bc374 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tuf"
-version = "0.1.0"
+version = "0.1.1"
authors = [ "heartsucker <heartsucker@autistici.org>" ]
description = "Library for The Update Framework (TUF)"
homepage = "https://github.com/heartsucker/rust-tuf"
diff --git a/README.md b/README.md
index a3ed744..b87f132 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,14 @@
# rust-tuf
-A rust implementation of The Update Framework (TUF).
+[![Travis build Status](https://travis-ci.org/heartsucker/rust-tuf.svg?branch=master)](https://travis-ci.org/heartsucker/rust-tuf) [![Appveyor build status](https://ci.appveyor.com/api/projects/status/kfyvpkdvn5ap7dqc?svg=true)](https://ci.appveyor.com/project/heartsucker/rust-tuf)
-## Warning: Alpha Software
+A Rust implementation of [The Update Framework (TUF)](https://theupdateframework.github.io/).
-This is under active development and is not suitable for production use. Further,
+Full documentation is hosted at [docs.rs](https://docs.rs/crate/tuf).
+
+## Warning: Beta Software
+
+This is under active development and may not suitable for production use. Further,
the API is unstable and you should be prepared to refactor on even patch releases.
## Contributing
diff --git a/src/lib.rs b/src/lib.rs
index 990ce2a..2b2f218 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,7 +19,7 @@
mod metadata;
mod error;
mod tuf;
-pub mod util;
+mod util;
pub use tuf::*;
pub use error::*;
diff --git a/src/main.rs b/src/main.rs
index 3e739f9..695546e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,9 +7,9 @@
extern crate tuf as _tuf;
extern crate url;
-use clap::{App, AppSettings, SubCommand, Arg, ArgMatches};
+use clap::{App, AppSettings, SubCommand, Arg, ArgMatches, ArgGroup};
use std::path::PathBuf;
-use _tuf::{Tuf, Config, Error};
+use _tuf::{Tuf, Config, Error, RemoteRepo};
use url::Url;
// TODO logging
@@ -28,7 +28,10 @@
}
fn run_main(matches: ArgMatches) -> Result<(), Error> {
- let config = Config::build().url(Url::parse(matches.value_of("url").unwrap())?)
+ let remote = matches.value_of("url").map(|u| RemoteRepo::Http(Url::parse(u).unwrap()))
+ .or_else(|| matches.value_of("file").map(|p| RemoteRepo::File(PathBuf::from(p))))
+ .unwrap();
+ let config = Config::build().remote(remote)
.local_path(PathBuf::from(matches.value_of("path").unwrap()))
.finish()?;
@@ -69,15 +72,22 @@
.short("U")
.long("url")
.takes_value(true)
- .required(true)
.validator(url_validator)
- .help("URL of the TUF repo (local or remote)"))
+ .help("URL of the TUF repo"))
+ .arg(Arg::with_name("file")
+ .short("f")
+ .long("file")
+ .takes_value(true)
+ .help("Path to the TUF repo (remote)"))
.arg(Arg::with_name("path")
.short("p")
.long("path")
.takes_value(true)
.required(true)
.help("Local path the TUF repo"))
+ .group(ArgGroup::with_name("remote_repo")
+ .args(&["url", "file"])
+ .required(true))
.subcommand(SubCommand::with_name("init").about("Initializes a new TUF repo"))
.subcommand(SubCommand::with_name("list").about("Lists available targets"))
.subcommand(SubCommand::with_name("update").about("Updates metadata from remotes"))
@@ -119,7 +129,6 @@
use std::fs::{self, DirBuilder};
use std::path::{Path, PathBuf};
use tempdir::TempDir;
- use _tuf::util;
fn vector_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -152,14 +161,14 @@
fn run_it() {
let temp = TempDir::new("rust-tuf").expect("couldn't make temp dir");
init_temp(temp.path());
- let url = util::path_to_url(&vector_path()).expect("bad path");
- println!("Test path: {:?}", temp.path());
- println!("Test URL: {:?}", url);
+ let path = vector_path();
+ println!("Temp path: {:?}", temp.path());
+ println!("Test path: {:?}", path);
let matches = parser()
.get_matches_from_safe(vec!["tuf",
- "--url",
- &url.to_string(),
+ "--file",
+ &path.to_string_lossy(),
"--path",
temp.path().to_str().expect("path not utf-8"),
"init"])
@@ -168,8 +177,8 @@
let matches = parser()
.get_matches_from_safe(vec!["tuf",
- "--url",
- &url.to_string(),
+ "--file",
+ &path.to_string_lossy(),
"--path",
temp.path().to_str().expect("path not utf-8"),
"update"])
@@ -178,8 +187,8 @@
let matches = parser()
.get_matches_from_safe(vec!["tuf",
- "--url",
- &url.to_string(),
+ "--file",
+ &path.to_string_lossy(),
"--path",
temp.path().to_str().expect("path not utf-8"),
"verify",
diff --git a/src/tuf.rs b/src/tuf.rs
index 0b1e9f2..336688e 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -19,10 +19,26 @@
use util;
+#[derive(Debug)]
+pub enum RemoteRepo {
+ File(PathBuf),
+ Http(Url),
+}
+
+impl RemoteRepo {
+ fn as_fetch(&self) -> FetchType {
+ match self {
+ &RemoteRepo::File(ref path) => FetchType::File(path.clone()),
+ &RemoteRepo::Http(ref url) => FetchType::Http(url.clone()),
+ }
+ }
+}
+
+
/// Interface for interacting with TUF repositories.
#[derive(Debug)]
pub struct Tuf {
- url: Url,
+ remote: RemoteRepo,
local_path: PathBuf,
http_client: Client,
root: RootMetadata,
@@ -50,11 +66,7 @@
}
Err(e) => {
debug!("Failed to read root locally: {:?}", e);
- let fetch_type = &match config.url.scheme() {
- "file" => FetchType::File(util::url_path_to_os_path(config.url.path())?),
- "http" | "https" => FetchType::Http(util::url_to_hyper_url(&config.url)?),
- x => return Err(Error::Generic(format!("Unsupported URL scheme: {}", x))),
- };
+ let fetch_type = &config.remote.as_fetch();
let modified_root =
Self::read_root_with_keys(fetch_type, &config.http_client, &root_keys)?;
Self::get_meta_num::<Root, RootMetadata, File>(fetch_type,
@@ -67,7 +79,7 @@
};
let mut tuf = Tuf {
- url: config.url,
+ remote: config.remote,
local_path: config.local_path,
http_client: config.http_client,
root: root,
@@ -96,7 +108,7 @@
};
let mut tuf = Tuf {
- url: config.url,
+ remote: config.remote,
local_path: config.local_path,
http_client: config.http_client,
root: root,
@@ -152,18 +164,7 @@
fn update_remote(&mut self) -> Result<(), Error> {
debug!("Updating metadata from remote sources");
- let fetch_type = &match self.url.scheme() {
- "file" => FetchType::File(util::url_path_to_os_path(self.url.path())?),
- "http" | "https" => FetchType::Http(util::url_to_hyper_url(&self.url)?),
- x => {
- let msg = format!("Programming error: unsupported URL scheme {}. Please report \
- this as a bug.",
- x);
- error!("{}", msg);
- return Err(Error::Generic(msg));
- }
- };
-
+ let fetch_type = &self.remote.as_fetch();
self.update_root(fetch_type)?;
if self.update_timestamp(fetch_type)? && self.update_snapshot(fetch_type)? {
@@ -826,16 +827,10 @@
} else {
let (out, out_path) = self.temp_file()?;
- match self.url.scheme() {
- "file" => {
- let mut url = self.url.clone();
- {
- url.path_segments_mut()
- .map_err(|_| Error::Generic("Path could not be mutated".to_string()))?
- .extend(util::url_path_to_os_path_components(target)?);
- }
-
- let path = util::url_path_to_os_path(url.path())?;
+ match self.remote {
+ RemoteRepo::File(ref path) => {
+ let mut path = path.clone();
+ path.extend(util::url_path_to_path_components(target)?);
let mut file = File::open(path.clone()).map_err(|e| Error::from_io(e, &path))?;
match Self::read_and_verify(&mut file,
@@ -845,7 +840,7 @@
Ok(()) => {
// TODO ensure intermediate directories exist
let mut storage_path = self.local_path.join("targets");
- storage_path.extend(util::url_path_to_os_path_components(target)?);
+ storage_path.extend(util::url_path_to_path_components(target)?);
{
let parent = storage_path.parent()
@@ -870,8 +865,14 @@
}
}
}
- "http" | "https" => {
- let url = util::url_to_hyper_url(&self.url.join(target)?)?;
+ RemoteRepo::Http(ref url) => {
+ let mut url = url.clone();
+ {
+ url.path_segments_mut()
+ .map_err(|_| Error::Generic("URL path could not be mutated".to_string()))?
+ .extend(util::url_path_to_path_components(&target)?);
+ }
+ let url = util::url_to_hyper_url(&url)?;
let mut resp = self.http_client.get(url).send()?;
match Self::read_and_verify(&mut resp,
@@ -882,7 +883,7 @@
// TODO this isn't windows friendly
// TODO ensure intermediate directories exist
let mut storage_path = self.local_path.join("targets");
- storage_path.extend(util::url_path_to_os_path_components(target)?);
+ storage_path.extend(util::url_path_to_path_components(target)?);
{
let parent = storage_path.parent()
@@ -908,7 +909,6 @@
}
}
}
- x => Err(Error::Generic(format!("Unsupported URL scheme: {}", x))),
}
}
}
@@ -989,9 +989,9 @@
/// The configuration used to initialize a `Tuf` struct.
pub struct Config {
- url: Url,
+ remote: RemoteRepo,
local_path: PathBuf,
- http_client: Client,
+ http_client: Client,
// TODO add `init: bool` to specify whether or not to create dir structure
}
@@ -1005,7 +1005,7 @@
/// Helper that constructs `Config`s and verifies the options.
pub struct ConfigBuilder {
- url: Option<Url>,
+ remote: Option<RemoteRepo>,
local_path: Option<PathBuf>,
http_client: Option<Client>,
}
@@ -1014,15 +1014,15 @@
/// Create a new builder with the default configurations where applicable.
pub fn new() -> Self {
ConfigBuilder {
- url: None,
+ remote: None,
local_path: None,
http_client: None,
}
}
- /// The remote URL of the TUF repo.
- pub fn url(mut self, url: Url) -> Self {
- self.url = Some(url);
+ /// The remote TUF repo.
+ pub fn remote(mut self, remote: RemoteRepo) -> Self {
+ self.remote = Some(remote);
self
}
@@ -1040,19 +1040,14 @@
/// Verify the configuration.
pub fn finish(self) -> Result<Config, Error> {
- let url = self.url
- .ok_or_else(|| Error::InvalidConfig("Repository URL was not set".to_string()))?;
-
- match url.scheme() {
- "file" | "http" | "https" => (),
- x => return Err(Error::InvalidConfig(format!("Unsupported URL scheme: {}", x))),
- };
+ let remote = self.remote
+ .ok_or_else(|| Error::InvalidConfig("Remote repository was not set".to_string()))?;
let local_path = self.local_path
.ok_or_else(|| Error::InvalidConfig("Local path was not set".to_string()))?;
Ok(Config {
- url: url,
+ remote: remote,
local_path: local_path,
http_client: self.http_client.unwrap_or_else(|| Client::new()),
})
diff --git a/src/util.rs b/src/util.rs
index 77c3945..67d561d 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,44 +1,10 @@
-//! Intended for internal use only, but made public for testing. This SHOULD NOT be used or relied
-//! upon.
-
use hyper;
-use std::path::{Path, PathBuf, Component};
+use std::path::{Path, PathBuf};
use url::Url;
-use url::percent_encoding::{percent_encode, percent_decode, DEFAULT_ENCODE_SET};
+use url::percent_encoding::percent_decode;
use error::Error;
-// TODO I'm pretty sure all of this path stuff is a mess, but at least the basics of
-// windows will work, right? Right?
-
-/// Converts a `Path` into a URL with scheme `file://`.
-pub fn path_to_url(path: &Path) -> Result<Url, Error> {
- // TODO handle sloppy to_string_lossy() calls
-
- let path_str = path.components()
- .fold(PathBuf::new(), |buf, c| match c {
- Component::Normal(os_str) => {
- buf.join(format!("{}",
- percent_encode(os_str.to_string_lossy().as_bytes(),
- DEFAULT_ENCODE_SET)))
- }
- Component::RootDir => buf.join("/"),
- Component::Prefix(pref) => {
- if cfg!(windows) {
- buf.join("/").join(pref.as_os_str().to_string_lossy().into_owned())
- } else {
- buf
- }
- }
- Component::CurDir => buf.join("."),
- Component::ParentDir => buf.join(".."),
- });
-
- Url::parse(&format!("file://{}",
- percent_encode(path_str.to_string_lossy().as_bytes(), DEFAULT_ENCODE_SET)))
- .map_err(|e| Error::Generic(format!("{}", e)))
-}
-
/// Converts a URL string (without scheme) into an OS specific path.
pub fn url_path_to_os_path(url_path: &str) -> Result<PathBuf, Error> {
let url_path = if cfg!(os = "windows") {
@@ -55,13 +21,12 @@
Ok(Path::new(&url_path).to_path_buf())
}
-pub fn url_path_to_os_path_components(url_path: &str) -> Result<Vec<String>, Error> {
+pub fn url_path_to_path_components(url_path: &str) -> Result<Vec<String>, Error> {
let mut out = Vec::new();
for component in url_path.split("/") {
let component = percent_decode(component.as_bytes())
.decode_utf8()
- .map_err(|e| Error::Generic(format!("Path component not utf-8: {:?}", e)))
- ?
+ .map_err(|e| Error::Generic(format!("Path component not utf-8: {:?}", e)))?
.into_owned();
out.push(component);
}
@@ -79,38 +44,6 @@
#[test]
#[cfg(not(target_os = "windows"))]
- fn test_path_to_url_nix() {
- let path = Path::new("/tmp/test");
- assert_eq!(path_to_url(path),
- Ok(Url::parse("file:///tmp/test").unwrap()));
- }
-
- #[test]
- #[cfg(not(target_os = "windows"))]
- fn test_path_to_url_spaces_nix() {
- let path = Path::new("/tmp/test stuff");
- assert_eq!(path_to_url(path),
- Ok(Url::parse("file:///tmp/test%20stuff").unwrap()));
- }
-
- #[test]
- #[cfg(target_os = "windows")]
- fn test_path_to_url_win() {
- let path = Path::new(r"C:\tmp\test");
- assert_eq!(path_to_url(path),
- Ok(Url::parse("file:///C:/tmp/test").unwrap()));
- }
-
- #[test]
- #[cfg(target_os = "windows")]
- fn test_path_to_url_spaces_win() {
- let path = Path::new(r"C:\tmp\test stuff");
- assert_eq!(path_to_url(path),
- Ok(Url::parse("file:///C:/tmp/test%20stuff").unwrap()));
- }
-
- #[test]
- #[cfg(not(target_os = "windows"))]
fn test_url_path_to_os_path_nix() {
let path = "/tmp/test";
assert_eq!(url_path_to_os_path(path), Ok(PathBuf::from("/tmp/test")));
@@ -140,11 +73,13 @@
}
#[test]
- fn test_url_path_to_os_path_components() {
+ fn test_url_path_to_path_components() {
let path = "test/foo";
- assert_eq!(url_path_to_os_path_components(path), Ok(vec!["test".into(), "foo".into()]));
+ assert_eq!(url_path_to_path_components(path),
+ Ok(vec!["test".into(), "foo".into()]));
let path = "test/foo%20bar";
- assert_eq!(url_path_to_os_path_components(path), Ok(vec!["test".into(), "foo bar".into()]));
+ assert_eq!(url_path_to_path_components(path),
+ Ok(vec!["test".into(), "foo bar".into()]));
}
}
diff --git a/tests/vectors.rs b/tests/vectors.rs
index 1c8ab9c..4766dcd 100644
--- a/tests/vectors.rs
+++ b/tests/vectors.rs
@@ -11,9 +11,8 @@
use std::io::Read;
use std::path::PathBuf;
use tempdir::TempDir;
-use tuf::{Tuf, Config, Error};
+use tuf::{Tuf, Config, Error, RemoteRepo};
use tuf::meta::{Key, KeyValue, KeyType};
-use tuf::util;
fn load_vector_meta() -> String {
@@ -56,7 +55,8 @@
let vector_meta: VectorMeta = json::from_str(&load_vector_meta())
.expect("couldn't deserializd meta");
- let test_vector = vector_meta.vectors.iter()
+ let test_vector = vector_meta.vectors
+ .iter()
.filter(|v| v.repo == test_path)
.collect::<Vec<&VectorMetaEntry>>()
.pop()
@@ -94,7 +94,7 @@
.collect();
let config = Config::build()
- .url(util::path_to_url(&vector_path.join("repo")).expect("couldn't make url"))
+ .remote(RemoteRepo::File(vector_path.join("repo")))
.local_path(temp_path.clone())
.finish()
.expect("bad config");