1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use plain;
use reqwest;
use serde_json;
use std::fs::File;
use std::io::{stdout, Read, Write};

use {err_str, Block, Manifest, Sha384};
use block::PackedBlock;
use store::b32dec;

pub struct DownloadArguments<'a> {
    pub project: &'a str,
    pub branch: &'a str,
    pub cert_opt: Option<&'a str>,
    pub cache_opt: Option<&'a str>,
    pub key: &'a str,
    pub url: &'a str,
    pub file_opt: Option<&'a str>,
}

pub struct Downloader {
    key: Vec<u8>,
    url: reqwest::Url,
    project: String,
    branch: String,
    client: reqwest::Client,
}

impl Downloader {
    pub fn new(key: &str, url: &str, project: &str, branch: &str, cert_opt: Option<&[u8]>) -> Result<Downloader, String> {
        let key = b32dec(key).ok_or(format!("key not in base32 format"))?;

        let url = reqwest::Url::parse(url).map_err(err_str)?;

        let client = {
            let mut builder = reqwest::Client::builder();

            if let Some(cert) = cert_opt {
                builder = builder.add_root_certificate(
                    reqwest::Certificate::from_pem(cert).map_err(err_str)?
                );
            }

            builder.build().map_err(err_str)?
        };

        Ok(Downloader {
            key: key,
            url: url,
            project: project.to_string(),
            branch: branch.to_string(),
            client: client,
        })
    }

    fn download(&self, path: &str) -> Result<Vec<u8>, String> {
        let url = self.url.join(path).map_err(err_str)?;
        let mut response = self.client.get(url).send().map_err(err_str)?;
        if ! response.status().is_success() {
            return Err(format!("failed to download {}: {:?}", path, response.status()));
        }

        let mut data = Vec::new();
        response.read_to_end(&mut data).map_err(err_str)?;
        Ok(data)
    }

    pub fn object(&self, digest: &str) -> Result<Vec<u8>, String> {
        let path = format!("object/{}", digest);
        let data = self.download(&path)?;

        let sha = Sha384::new(data.as_slice()).map_err(err_str)?;
        if &sha.to_base32() != digest {
            return Err(format!("sha384 mismatch"));
        }

        Ok(data)
    }

    pub fn tail(&self) -> Result<Block, String> {
        let path = format!("tail/{}/{}", self.project, self.branch);
        let data = self.download(&path)?;

        let b: &PackedBlock = plain::from_bytes(&data).map_err(|_| format!("response too small"))?;
        b.verify(&self.key)
    }
}

pub fn download<'a>(args: DownloadArguments<'a>) -> Result<(), String> {
    let mut cert = Vec::new();
    let cert_opt = if let Some(cert_path) = args.cert_opt {
        {
            let mut file = File::open(cert_path).map_err(err_str)?;
            file.read_to_end(&mut cert).map_err(err_str)?;
        }
        Some(cert.as_slice())
    } else {
        None
    };

    let dl = Downloader::new(
        args.key,
        args.url,
        args.project,
        args.branch,
        cert_opt,
    )?;

    let tail = dl.tail()?;

    let manifest_json = dl.object(&tail.digest)?;
    let manifest = serde_json::from_slice::<Manifest>(&manifest_json).map_err(err_str)?;

    if let Some(file) = args.file_opt {
        if let Some(digest) = manifest.files.get(file) {
            let data = dl.object(digest)?;
            stdout().write(&data).map_err(err_str)?;
        } else {
            return Err(format!("{} not found", file));
        }
    } else {
        for (file, digest) in manifest.files.iter() {
            println!("{}", file);
        }
    }

    Ok(())
}