<commit_before>//! rustdoc handler

use super::pool::Pool;
use super::file::File;
use super::{latest_version, redirect_base};
use super::crate_details::CrateDetails;
use iron::prelude::*;
use iron::{status, Url};
use iron::modifiers::Redirect;
use router::Router;
use super::{match_version, MatchVersion};
use super::error::Nope;
use super::page::Page;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use iron::headers::{Expires, HttpDate, CacheControl, CacheDirective};
use time;
use iron::Handler;
use utils;

#[derive(Debug)]
struct RustdocPage {
    pub head: String,
    pub body: String,
    pub body_class: String,
    pub name: String,
    pub full: String,
    pub version: String,
    pub description: Option<String>,
    pub crate_details: Option<CrateDetails>,
}

impl Default for RustdocPage {
    fn default() -> RustdocPage {
        RustdocPage {
            head: String::new(),
            body: String::new(),
            body_class: String::new(),
            name: String::new(),
            full: String::new(),
            version: String::new(),
            description: None,
            crate_details: None,
        }
    }
}

impl ToJson for RustdocPage {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rustdoc_head".to_string(), self.head.to_json());
        m.insert("rustdoc_body".to_string(), self.body.to_json());
        m.insert("rustdoc_body_class".to_string(), self.body_class.to_json());
        m.insert("rustdoc_full".to_string(), self.full.to_json());
        m.insert("rustdoc_status".to_string(), true.to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("crate_details".to_string(), self.crate_details.to_json());
        m.to_json()
    }
}

pub struct RustLangRedirector {
    url: Url,
}

impl RustLangRedirector {
    pub fn new(target: &'static str) -> Self {
        let url = url::Url::parse("https://doc.rust-lang.org/stable/")
            .expect("failed to parse rust-lang.org base URL")
            .join(target)
            .expect("failed to append crate name to rust-lang.org base URL");
        let url = Url::from_generic_url(url)
            .expect("failed to convert url::Url to iron::Url");
        Self { url }
    }
}

impl iron::Handler for RustLangRedirector {
    fn handle(&self, _req: &mut Request) -> IronResult<Response> {
        Ok(Response::with((status::Found, Redirect(self.url.clone()))))
    }
}

/// Handler called for `/:crate` and `/:crate/:version` URLs. Automatically redirects to the docs
/// or crate details page based on whether the given crate version was successfully built.
pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {

    fn redirect_to_doc(req: &Request,
                       name: &str,
                       vers: &str,
                       target_name: &str)
                       -> IronResult<Response> {
        let url = ctry!(Url::parse(&format!("{}/{}/{}/{}/",
                                            redirect_base(req),
                                            name,
                                            vers,
                                            target_name)[..]));
        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    fn redirect_to_crate(req: &Request,
                         name: &str,
                         vers: &str)
                         -> IronResult<Response> {
        let url = ctry!(Url::parse(&format!("{}/crate/{}/{}",
                                            redirect_base(req),
                                            name,
                                            vers)[..]));

        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    // this unwrap is safe because iron urls are always able to use `path_segments`
    // i'm using this instead of `req.url.path()` to avoid allocating the Vec, and also to avoid
    // keeping the borrow alive into the return statement
    if req.url.as_ref().path_segments().unwrap().last().map_or(false, |s| s.ends_with(".js")) {
        // javascript files should be handled by the file server instead of erroneously
        // redirecting to the crate root page
        if req.url.as_ref().path_segments().unwrap().count() > 2 {
            // this URL is actually from a crate-internal path, serve it there instead
            return rustdoc_html_server_handler(req);
        } else {
            let path = req.url.path();
            let path = path.join("/");
            let conn = extension!(req, Pool);
            match File::from_path(&conn, &path) {
                Some(f) => return Ok(f.serve()),
                None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
            }
        }
    } else if req.url.as_ref().path_segments().unwrap().last().map_or(false, |s| s.ends_with(".ico")) {
        // route .ico files into their dedicated handler so that docs.rs's favicon is always
        // displayed
        return super::ico_handler(req);
    }

    let router = extension!(req, Router);
    // this handler should never called without crate pattern
    let crate_name = cexpect!(router.find("crate"));
    let req_version = router.find("version");

    let conn = extension!(req, Pool);

    // it doesn't matter if the version that was given was exact or not, since we're redirecting
    // anyway
    let version = match match_version(&conn, &crate_name, req_version).into_option() {
        Some(v) => v,
        None => return Err(IronError::new(Nope::CrateNotFound, status::NotFound)),
    };

    // get target name and whether it has docs
    // FIXME: This is a bit inefficient but allowing us to use less code in general
    let (target_name, has_docs): (String, bool) = {
        let rows = ctry!(conn.query("SELECT target_name, rustdoc_status
                                     FROM releases
                                     INNER JOIN crates ON crates.id = releases.crate_id
                                     WHERE crates.name = $1 AND releases.version = $2",
                                    &[&crate_name, &version]));

        (rows.get(0).get(0), rows.get(0).get(1))
    };

    if has_docs {
        redirect_to_doc(req, &crate_name, &version, &target_name)
    } else {
        redirect_to_crate(req, &crate_name, &version)
    }
}

/// Serves documentation generated by rustdoc.
///
/// This includes all HTML files for an individual crate, as well as the `search-index.js`, which is
/// also crate-specific.
pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {

    let router = extension!(req, Router);
    let name = router.find("crate").unwrap_or("").to_string();
    let url_version = router.find("version");
    let version; // pre-declaring it to enforce drop order relative to `req_path`
    let conn = extension!(req, Pool);

    let mut req_path = req.url.path();

    // remove name and version from path
    for _ in 0..2 {
        req_path.remove(0);
    }

    version = match match_version(&conn, &name, url_version) {
        MatchVersion::Exact(v) => v,
        MatchVersion::Semver(v) => {
            // to prevent cloudfront caching the wrong artifacts on URLs with loose semver
            // versions, redirect the browser to the returned version instead of loading it
            // immediately
            let url = ctry!(Url::parse(&format!("{}/{}/{}/{}",
                                                redirect_base(req),
                                                name,
                                                v,
                                                req_path.join("/"))[..]));
            return Ok(super::redirect(url));
        }
        MatchVersion::None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    // docs have "rustdoc" prefix in database
    req_path.insert(0, "rustdoc");

    // add crate name and version
    req_path.insert(1, &name);
    req_path.insert(2, &version);

    let path = {
        let mut path = req_path.join("/");
        if path.ends_with('/') {
            req_path.pop(); // get rid of empty string
            path.push_str("index.html");
            req_path.push("index.html");
        }
        path
    };

    let file = match File::from_path(&conn, &path) {
        Some(f) => f,
        None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    // serve file directly if it's not html
    if !path.ends_with(".html") {
        return Ok(file.serve());
    }

    let mut content = RustdocPage::default();

    let file_content = ctry!(String::from_utf8(file.0.content));

    let (head, body, mut body_class) = ctry!(utils::extract_head_and_body(&file_content));
    content.head = head;
    content.body = body;

    if body_class.is_empty() {
        body_class = "rustdoc container-rustdoc".to_string();
    } else {
        // rustdoc adds its own "rustdoc" class to the body
        body_class.push_str(" container-rustdoc");
    }
    content.body_class = body_class;

    content.full = file_content;
    let crate_details = cexpect!(CrateDetails::new(&conn, &name, &version));
    let latest_version = latest_version(&crate_details.versions, &version);

    let (path, version) = if let Some(version) = latest_version {
        req_path[2] = &version;
        let path = if File::from_path(&conn, &req_path.join("/")).is_some() {
            req_path[3..].join("/") // NOTE: this adds 'index.html' if it wasn't there before
        } else { // this page doesn't exist in the latest version
            let search_item = if *req_path.last().unwrap() == "index.html" { // this is a module
                req_path[&req_path.len() - 2]
            } else { // this is an item
                req_path.last().unwrap().split('.').nth(1)
                    .expect("paths should be of the form <class>.<name>.html")
            };
            // check if req_path[3] is the platform choice or the name of the crate
            let concat_path;
            let crate_root = if req_path[3] != crate_details.target_name {
                concat_path = format!("{}/{}", req_path[3], req_path[4]);
                &concat_path
            } else {
                req_path[3]
            };
            format!("{}/?search={}", crate_root, search_item)
        };
        (path, version)
    } else {
        (String::new(), String::new())
    };

    content.crate_details = Some(crate_details);

    Page::new(content)
        .set_true("show_package_navigation")
        .set_true("package_navigation_documentation_tab")
        .set_true("package_navigation_show_platforms_tab")
        .set_bool("is_latest_version", path.is_empty())
        .set("path_in_latest", &path)
        .set("latest_version", &version)
        .to_resp("rustdoc")
}

pub fn badge_handler(req: &mut Request) -> IronResult<Response> {
    use iron::headers::ContentType;
    use params::{Params, Value};
    use badge::{Badge, BadgeOptions};

    let version = {
        let params = ctry!(req.get_ref::<Params>());
        match params.find(&["version"]) {
            Some(&Value::String(ref version)) => version.clone(),
            _ => "*".to_owned(),
        }
    };

    let name = cexpect!(extension!(req, Router).find("crate"));
    let conn = extension!(req, Pool);

    let options = match match_version(&conn, &name, Some(&version)) {
        MatchVersion::Exact(version) => {
            let rows = ctry!(conn.query("SELECT rustdoc_status
                                         FROM releases
                                         INNER JOIN crates ON crates.id = releases.crate_id
                                         WHERE crates.name = $1 AND releases.version = $2",
                                        &[&name, &version]));
            if rows.len() > 0 && rows.get(0).get(0) {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#4d76ae".to_owned(),
                }
            } else {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#e05d44".to_owned(),
                }
            }
        }
        MatchVersion::Semver(version) => {
            let url = ctry!(Url::parse(&format!("{}/{}/badge.svg?version={}",
                                                redirect_base(req),
                                                name,
                                                version)[..]));

            return Ok(super::redirect(url));
        }
        MatchVersion::None => {
            BadgeOptions {
                subject: "docs".to_owned(),
                status: "no builds".to_owned(),
                color: "#e05d44".to_owned(),
            }
        }
    };

    let mut resp = Response::with((status::Ok, ctry!(Badge::new(options)).to_svg()));
    resp.headers.set(ContentType("image/svg+xml".parse().unwrap()));
    resp.headers.set(Expires(HttpDate(time::now())));
    resp.headers.set(CacheControl(vec![CacheDirective::NoCache,
                                       CacheDirective::NoStore,
                                       CacheDirective::MustRevalidate]));
    Ok(resp)
}

/// Serves shared web resources used by rustdoc-generated documentation.
///
/// This includes common `css` and `js` files that only change when the compiler is updated, but are
/// otherwise the same for all crates documented with that compiler. Those have a custom handler to
/// deduplicate them and save space.
pub struct SharedResourceHandler;

impl Handler for SharedResourceHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let path = req.url.path();
        let filename = path.last().unwrap();  // unwrap is fine: vector is non-empty
        let suffix = filename.split('.').last().unwrap();  // unwrap is fine: split always works
        if ["js", "css", "woff", "svg"].contains(&suffix) {
            let conn = extension!(req, Pool);

            if let Some(file) = File::from_path(conn, filename) {
                return Ok(file.serve());
            }
        }

        // Just always return a 404 here - the main handler will then try the other handlers
        Err(IronError::new(Nope::ResourceNotFound, status::NotFound))
    }
}
<commit_msg>refactor logic into a function<commit_after>//! rustdoc handler

use super::pool::Pool;
use super::file::File;
use super::{latest_version, redirect_base};
use super::crate_details::CrateDetails;
use iron::prelude::*;
use iron::{status, Url};
use iron::modifiers::Redirect;
use router::Router;
use super::{match_version, MatchVersion};
use super::error::Nope;
use super::page::Page;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use iron::headers::{Expires, HttpDate, CacheControl, CacheDirective};
use postgres::Connection;
use time;
use iron::Handler;
use utils;

#[derive(Debug)]
struct RustdocPage {
    pub head: String,
    pub body: String,
    pub body_class: String,
    pub name: String,
    pub full: String,
    pub version: String,
    pub description: Option<String>,
    pub crate_details: Option<CrateDetails>,
}

impl Default for RustdocPage {
    fn default() -> RustdocPage {
        RustdocPage {
            head: String::new(),
            body: String::new(),
            body_class: String::new(),
            name: String::new(),
            full: String::new(),
            version: String::new(),
            description: None,
            crate_details: None,
        }
    }
}

impl ToJson for RustdocPage {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rustdoc_head".to_string(), self.head.to_json());
        m.insert("rustdoc_body".to_string(), self.body.to_json());
        m.insert("rustdoc_body_class".to_string(), self.body_class.to_json());
        m.insert("rustdoc_full".to_string(), self.full.to_json());
        m.insert("rustdoc_status".to_string(), true.to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("crate_details".to_string(), self.crate_details.to_json());
        m.to_json()
    }
}

pub struct RustLangRedirector {
    url: Url,
}

impl RustLangRedirector {
    pub fn new(target: &'static str) -> Self {
        let url = url::Url::parse("https://doc.rust-lang.org/stable/")
            .expect("failed to parse rust-lang.org base URL")
            .join(target)
            .expect("failed to append crate name to rust-lang.org base URL");
        let url = Url::from_generic_url(url)
            .expect("failed to convert url::Url to iron::Url");
        Self { url }
    }
}

impl iron::Handler for RustLangRedirector {
    fn handle(&self, _req: &mut Request) -> IronResult<Response> {
        Ok(Response::with((status::Found, Redirect(self.url.clone()))))
    }
}

/// Handler called for `/:crate` and `/:crate/:version` URLs. Automatically redirects to the docs
/// or crate details page based on whether the given crate version was successfully built.
pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {

    fn redirect_to_doc(req: &Request,
                       name: &str,
                       vers: &str,
                       target_name: &str)
                       -> IronResult<Response> {
        let url = ctry!(Url::parse(&format!("{}/{}/{}/{}/",
                                            redirect_base(req),
                                            name,
                                            vers,
                                            target_name)[..]));
        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    fn redirect_to_crate(req: &Request,
                         name: &str,
                         vers: &str)
                         -> IronResult<Response> {
        let url = ctry!(Url::parse(&format!("{}/crate/{}/{}",
                                            redirect_base(req),
                                            name,
                                            vers)[..]));

        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    // this unwrap is safe because iron urls are always able to use `path_segments`
    // i'm using this instead of `req.url.path()` to avoid allocating the Vec, and also to avoid
    // keeping the borrow alive into the return statement
    if req.url.as_ref().path_segments().unwrap().last().map_or(false, |s| s.ends_with(".js")) {
        // javascript files should be handled by the file server instead of erroneously
        // redirecting to the crate root page
        if req.url.as_ref().path_segments().unwrap().count() > 2 {
            // this URL is actually from a crate-internal path, serve it there instead
            return rustdoc_html_server_handler(req);
        } else {
            let path = req.url.path();
            let path = path.join("/");
            let conn = extension!(req, Pool);
            match File::from_path(&conn, &path) {
                Some(f) => return Ok(f.serve()),
                None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
            }
        }
    } else if req.url.as_ref().path_segments().unwrap().last().map_or(false, |s| s.ends_with(".ico")) {
        // route .ico files into their dedicated handler so that docs.rs's favicon is always
        // displayed
        return super::ico_handler(req);
    }

    let router = extension!(req, Router);
    // this handler should never called without crate pattern
    let crate_name = cexpect!(router.find("crate"));
    let req_version = router.find("version");

    let conn = extension!(req, Pool);

    // it doesn't matter if the version that was given was exact or not, since we're redirecting
    // anyway
    let version = match match_version(&conn, &crate_name, req_version).into_option() {
        Some(v) => v,
        None => return Err(IronError::new(Nope::CrateNotFound, status::NotFound)),
    };

    // get target name and whether it has docs
    // FIXME: This is a bit inefficient but allowing us to use less code in general
    let (target_name, has_docs): (String, bool) = {
        let rows = ctry!(conn.query("SELECT target_name, rustdoc_status
                                     FROM releases
                                     INNER JOIN crates ON crates.id = releases.crate_id
                                     WHERE crates.name = $1 AND releases.version = $2",
                                    &[&crate_name, &version]));

        (rows.get(0).get(0), rows.get(0).get(1))
    };

    if has_docs {
        redirect_to_doc(req, &crate_name, &version, &target_name)
    } else {
        redirect_to_crate(req, &crate_name, &version)
    }
}

/// Serves documentation generated by rustdoc.
///
/// This includes all HTML files for an individual crate, as well as the `search-index.js`, which is
/// also crate-specific.
pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {

    let router = extension!(req, Router);
    let name = router.find("crate").unwrap_or("").to_string();
    let url_version = router.find("version");
    let version; // pre-declaring it to enforce drop order relative to `req_path`
    let conn = extension!(req, Pool);

    let mut req_path = req.url.path();

    // remove name and version from path
    for _ in 0..2 {
        req_path.remove(0);
    }

    version = match match_version(&conn, &name, url_version) {
        MatchVersion::Exact(v) => v,
        MatchVersion::Semver(v) => {
            // to prevent cloudfront caching the wrong artifacts on URLs with loose semver
            // versions, redirect the browser to the returned version instead of loading it
            // immediately
            let url = ctry!(Url::parse(&format!("{}/{}/{}/{}",
                                                redirect_base(req),
                                                name,
                                                v,
                                                req_path.join("/"))[..]));
            return Ok(super::redirect(url));
        }
        MatchVersion::None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    // docs have "rustdoc" prefix in database
    req_path.insert(0, "rustdoc");

    // add crate name and version
    req_path.insert(1, &name);
    req_path.insert(2, &version);

    let path = {
        let mut path = req_path.join("/");
        if path.ends_with('/') {
            req_path.pop(); // get rid of empty string
            path.push_str("index.html");
            req_path.push("index.html");
        }
        path
    };

    let file = match File::from_path(&conn, &path) {
        Some(f) => f,
        None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    // serve file directly if it's not html
    if !path.ends_with(".html") {
        return Ok(file.serve());
    }

    let mut content = RustdocPage::default();

    let file_content = ctry!(String::from_utf8(file.0.content));

    let (head, body, mut body_class) = ctry!(utils::extract_head_and_body(&file_content));
    content.head = head;
    content.body = body;

    if body_class.is_empty() {
        body_class = "rustdoc container-rustdoc".to_string();
    } else {
        // rustdoc adds its own "rustdoc" class to the body
        body_class.push_str(" container-rustdoc");
    }
    content.body_class = body_class;

    content.full = file_content;
    let crate_details = cexpect!(CrateDetails::new(&conn, &name, &version));
    let (path, version) = if let Some(version) = latest_version(&crate_details.versions, &version) {
        req_path[2] = &version;
        (path_for_version(&req_path, &crate_details.target_name, &conn), version)
    } else {
        Default::default()
    };

    content.crate_details = Some(crate_details);

    Page::new(content)
        .set_true("show_package_navigation")
        .set_true("package_navigation_documentation_tab")
        .set_true("package_navigation_show_platforms_tab")
        .set_bool("is_latest_version", path.is_empty())
        .set("path_in_latest", &path)
        .set("latest_version", &version)
        .to_resp("rustdoc")
}

fn path_for_version(req_path: &[&str], target_name: &str, conn: &Connection) -> String {
    if File::from_path(&conn, &req_path.join("/")).is_some() {
        req_path[3..].join("/") // NOTE: this adds 'index.html' if it wasn't there before
    } else { // this page doesn't exist in the latest version
        let search_item = if *req_path.last().unwrap() == "index.html" { // this is a module
            req_path[req_path.len() - 2]
        } else { // this is an item
            req_path.last().unwrap().split('.').nth(1)
                .expect("paths should be of the form <class>.<name>.html")
        };
        // check if req_path[3] is the platform choice or the name of the crate
        let concat_path;
        let crate_root = if req_path[3] != target_name {
            concat_path = format!("{}/{}", req_path[3], req_path[4]);
            &concat_path
        } else {
            req_path[3]
        };
        format!("{}/?search={}", crate_root, search_item)
    }
}

pub fn badge_handler(req: &mut Request) -> IronResult<Response> {
    use iron::headers::ContentType;
    use params::{Params, Value};
    use badge::{Badge, BadgeOptions};

    let version = {
        let params = ctry!(req.get_ref::<Params>());
        match params.find(&["version"]) {
            Some(&Value::String(ref version)) => version.clone(),
            _ => "*".to_owned(),
        }
    };

    let name = cexpect!(extension!(req, Router).find("crate"));
    let conn = extension!(req, Pool);

    let options = match match_version(&conn, &name, Some(&version)) {
        MatchVersion::Exact(version) => {
            let rows = ctry!(conn.query("SELECT rustdoc_status
                                         FROM releases
                                         INNER JOIN crates ON crates.id = releases.crate_id
                                         WHERE crates.name = $1 AND releases.version = $2",
                                        &[&name, &version]));
            if rows.len() > 0 && rows.get(0).get(0) {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#4d76ae".to_owned(),
                }
            } else {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#e05d44".to_owned(),
                }
            }
        }
        MatchVersion::Semver(version) => {
            let url = ctry!(Url::parse(&format!("{}/{}/badge.svg?version={}",
                                                redirect_base(req),
                                                name,
                                                version)[..]));

            return Ok(super::redirect(url));
        }
        MatchVersion::None => {
            BadgeOptions {
                subject: "docs".to_owned(),
                status: "no builds".to_owned(),
                color: "#e05d44".to_owned(),
            }
        }
    };

    let mut resp = Response::with((status::Ok, ctry!(Badge::new(options)).to_svg()));
    resp.headers.set(ContentType("image/svg+xml".parse().unwrap()));
    resp.headers.set(Expires(HttpDate(time::now())));
    resp.headers.set(CacheControl(vec![CacheDirective::NoCache,
                                       CacheDirective::NoStore,
                                       CacheDirective::MustRevalidate]));
    Ok(resp)
}

/// Serves shared web resources used by rustdoc-generated documentation.
///
/// This includes common `css` and `js` files that only change when the compiler is updated, but are
/// otherwise the same for all crates documented with that compiler. Those have a custom handler to
/// deduplicate them and save space.
pub struct SharedResourceHandler;

impl Handler for SharedResourceHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let path = req.url.path();
        let filename = path.last().unwrap();  // unwrap is fine: vector is non-empty
        let suffix = filename.split('.').last().unwrap();  // unwrap is fine: split always works
        if ["js", "css", "woff", "svg"].contains(&suffix) {
            let conn = extension!(req, Pool);

            if let Some(file) = File::from_path(conn, filename) {
                return Ok(file.serve());
            }
        }

        // Just always return a 404 here - the main handler will then try the other handlers
        Err(IronError::new(Nope::ResourceNotFound, status::NotFound))
    }
}
<|endoftext|>


<commit_before>use super::Blob;
use failure::Error;
use futures::Future;
use log::{error, warn};
use rusoto_core::region::Region;
use rusoto_credential::DefaultCredentialsProvider;
use rusoto_s3::{GetObjectRequest, PutObjectRequest, S3Client, S3};
use std::convert::TryInto;
use std::io::Read;
use time::Timespec;
use tokio::runtime::Runtime;

pub(crate) struct S3Backend<'a> {
    client: S3Client,
    bucket: &'a str,
    runtime: Runtime,
}

impl<'a> S3Backend<'a> {
    pub(crate) fn new(client: S3Client, bucket: &'a str) -> Self {
        Self { client, bucket, runtime: Runtime::new().unwrap() }
    }

    pub(super) fn get(&self, path: &str) -> Result<Blob, Error> {
        let res = self
            .client
            .get_object(GetObjectRequest {
                bucket: self.bucket.to_string(),
                key: path.into(),
                ..Default::default()
            })
            .sync()?;

        let mut b = res.body.unwrap().into_blocking_read();
        let mut content = Vec::with_capacity(
            res.content_length
                .and_then(|l| l.try_into().ok())
                .unwrap_or(0),
        );
        b.read_to_end(&mut content).unwrap();

        let date_updated = parse_timespec(&res.last_modified.unwrap())?;

        Ok(Blob {
            path: path.into(),
            mime: res.content_type.unwrap(),
            date_updated,
            content,
        })
    }

    pub(super) fn store_batch(&mut self, batch: &[Blob]) -> Result<(), Error> {
        use futures::stream::FuturesUnordered;
        use futures::stream::Stream;
        let mut attempts = 0;

        loop {
            let mut futures = FuturesUnordered::new();
            for blob in batch {
                futures.push(
                    self.client
                        .put_object(PutObjectRequest {
                            bucket: self.bucket.to_string(),
                            key: blob.path.clone(),
                            body: Some(blob.content.clone().into()),
                            content_type: Some(blob.mime.clone()),
                            ..Default::default()
                        })
                        .inspect(|_| {
                            crate::web::metrics::UPLOADED_FILES_TOTAL.inc_by(1);
                        }),
                );
            }
            attempts += 1;

            match self.runtime.block_on(futures.collect()) {
                // this batch was successful, start another batch if there are still more files
                Ok(_) => break,
                Err(err) => {
                    error!("failed to upload to s3: {:?}", err);
                    // if a futures error occurs, retry the batch
                    if attempts > 2 {
                        panic!("failed to upload 3 times, exiting");
                    }
                }
            }
        }
        Ok(())
    }
}

// public for testing
pub(crate) const TIME_FMT: &str = "%a, %d %b %Y %H:%M:%S %Z";

fn parse_timespec(raw: &str) -> Result<Timespec, Error> {
    Ok(time::strptime(raw, TIME_FMT)?.to_timespec())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::TestEnvironment;

    fn assert_s3_404(env: &TestEnvironment, path: &'static str) {
        use rusoto_core::RusotoError;
        use rusoto_s3::GetObjectError;

        let s3 = env.s3().not_found(path);
        let backend = S3Backend::new(s3.client, s3.bucket);
        let err = backend.get(path).unwrap_err();
        let status = match err
            .downcast_ref::<RusotoError<GetObjectError>>()
            .expect("wanted GetObject")
        {
            RusotoError::Unknown(http) => http.status,
            _ => panic!("wrong error"),
        };
        assert_eq!(status, 404);
    }

    #[test]
    fn test_parse_timespec() {
        crate::test::wrapper(|_| {
            // Test valid conversions
            assert_eq!(
                parse_timespec("Thu, 1 Jan 1970 00:00:00 GMT")?,
                Timespec::new(0, 0)
            );
            assert_eq!(
                parse_timespec("Mon, 16 Apr 2018 04:33:50 GMT")?,
                Timespec::new(1523853230, 0)
            );

            // Test invalid conversion
            assert!(parse_timespec("foo").is_err());

            Ok(())
        })
    }

    #[test]
    fn test_path_get() {
        crate::test::wrapper(|env| {
            let blob = Blob {
                path: "dir/foo.txt".into(),
                mime: "text/plain".into(),
                date_updated: Timespec::new(42, 0),
                content: "Hello world!".into(),
            };

            // Add a test file to the database
            let s3 = env.s3().upload(blob.clone());

            let backend = S3Backend::new(s3.client, &s3.bucket);

            // Test that the proper file was returned
            assert_eq!(blob, backend.get("dir/foo.txt")?);

            // Test that other files are not returned
            assert_s3_404(&env, "dir/bar.txt");
            assert_s3_404(&env, "foo.txt");

            Ok(())
        });
    }
}

pub(crate) static S3_BUCKET_NAME: &str = "rust-docs-rs";

pub(crate) fn s3_client() -> Option<S3Client> {
    // If AWS keys aren't configured, then presume we should use the DB exclusively
    // for file storage.
    if std::env::var_os("AWS_ACCESS_KEY_ID").is_none() && std::env::var_os("FORCE_S3").is_none() {
        return None;
    }
    let creds = match DefaultCredentialsProvider::new() {
        Ok(creds) => creds,
        Err(err) => {
            warn!("failed to retrieve AWS credentials: {}", err);
            return None;
        }
    };
    Some(S3Client::new_with(
        rusoto_core::request::HttpClient::new().unwrap(),
        creds,
        std::env::var("S3_ENDPOINT")
            .ok()
            .map(|e| Region::Custom {
                name: std::env::var("S3_REGION").unwrap_or_else(|_| "us-west-1".to_owned()),
                endpoint: e,
            })
            .unwrap_or(Region::UsWest1),
    ))
}
<commit_msg>Make rustfmt happy<commit_after>use super::Blob;
use failure::Error;
use futures::Future;
use log::{error, warn};
use rusoto_core::region::Region;
use rusoto_credential::DefaultCredentialsProvider;
use rusoto_s3::{GetObjectRequest, PutObjectRequest, S3Client, S3};
use std::convert::TryInto;
use std::io::Read;
use time::Timespec;
use tokio::runtime::Runtime;

pub(crate) struct S3Backend<'a> {
    client: S3Client,
    bucket: &'a str,
    runtime: Runtime,
}

impl<'a> S3Backend<'a> {
    pub(crate) fn new(client: S3Client, bucket: &'a str) -> Self {
        Self {
            client,
            bucket,
            runtime: Runtime::new().unwrap(),
        }
    }

    pub(super) fn get(&self, path: &str) -> Result<Blob, Error> {
        let res = self
            .client
            .get_object(GetObjectRequest {
                bucket: self.bucket.to_string(),
                key: path.into(),
                ..Default::default()
            })
            .sync()?;

        let mut b = res.body.unwrap().into_blocking_read();
        let mut content = Vec::with_capacity(
            res.content_length
                .and_then(|l| l.try_into().ok())
                .unwrap_or(0),
        );
        b.read_to_end(&mut content).unwrap();

        let date_updated = parse_timespec(&res.last_modified.unwrap())?;

        Ok(Blob {
            path: path.into(),
            mime: res.content_type.unwrap(),
            date_updated,
            content,
        })
    }

    pub(super) fn store_batch(&mut self, batch: &[Blob]) -> Result<(), Error> {
        use futures::stream::FuturesUnordered;
        use futures::stream::Stream;
        let mut attempts = 0;

        loop {
            let mut futures = FuturesUnordered::new();
            for blob in batch {
                futures.push(
                    self.client
                        .put_object(PutObjectRequest {
                            bucket: self.bucket.to_string(),
                            key: blob.path.clone(),
                            body: Some(blob.content.clone().into()),
                            content_type: Some(blob.mime.clone()),
                            ..Default::default()
                        })
                        .inspect(|_| {
                            crate::web::metrics::UPLOADED_FILES_TOTAL.inc_by(1);
                        }),
                );
            }
            attempts += 1;

            match self.runtime.block_on(futures.collect()) {
                // this batch was successful, start another batch if there are still more files
                Ok(_) => break,
                Err(err) => {
                    error!("failed to upload to s3: {:?}", err);
                    // if a futures error occurs, retry the batch
                    if attempts > 2 {
                        panic!("failed to upload 3 times, exiting");
                    }
                }
            }
        }
        Ok(())
    }
}

// public for testing
pub(crate) const TIME_FMT: &str = "%a, %d %b %Y %H:%M:%S %Z";

fn parse_timespec(raw: &str) -> Result<Timespec, Error> {
    Ok(time::strptime(raw, TIME_FMT)?.to_timespec())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test::TestEnvironment;

    fn assert_s3_404(env: &TestEnvironment, path: &'static str) {
        use rusoto_core::RusotoError;
        use rusoto_s3::GetObjectError;

        let s3 = env.s3().not_found(path);
        let backend = S3Backend::new(s3.client, s3.bucket);
        let err = backend.get(path).unwrap_err();
        let status = match err
            .downcast_ref::<RusotoError<GetObjectError>>()
            .expect("wanted GetObject")
        {
            RusotoError::Unknown(http) => http.status,
            _ => panic!("wrong error"),
        };
        assert_eq!(status, 404);
    }

    #[test]
    fn test_parse_timespec() {
        crate::test::wrapper(|_| {
            // Test valid conversions
            assert_eq!(
                parse_timespec("Thu, 1 Jan 1970 00:00:00 GMT")?,
                Timespec::new(0, 0)
            );
            assert_eq!(
                parse_timespec("Mon, 16 Apr 2018 04:33:50 GMT")?,
                Timespec::new(1523853230, 0)
            );

            // Test invalid conversion
            assert!(parse_timespec("foo").is_err());

            Ok(())
        })
    }

    #[test]
    fn test_path_get() {
        crate::test::wrapper(|env| {
            let blob = Blob {
                path: "dir/foo.txt".into(),
                mime: "text/plain".into(),
                date_updated: Timespec::new(42, 0),
                content: "Hello world!".into(),
            };

            // Add a test file to the database
            let s3 = env.s3().upload(blob.clone());

            let backend = S3Backend::new(s3.client, &s3.bucket);

            // Test that the proper file was returned
            assert_eq!(blob, backend.get("dir/foo.txt")?);

            // Test that other files are not returned
            assert_s3_404(&env, "dir/bar.txt");
            assert_s3_404(&env, "foo.txt");

            Ok(())
        });
    }
}

pub(crate) static S3_BUCKET_NAME: &str = "rust-docs-rs";

pub(crate) fn s3_client() -> Option<S3Client> {
    // If AWS keys aren't configured, then presume we should use the DB exclusively
    // for file storage.
    if std::env::var_os("AWS_ACCESS_KEY_ID").is_none() && std::env::var_os("FORCE_S3").is_none() {
        return None;
    }
    let creds = match DefaultCredentialsProvider::new() {
        Ok(creds) => creds,
        Err(err) => {
            warn!("failed to retrieve AWS credentials: {}", err);
            return None;
        }
    };
    Some(S3Client::new_with(
        rusoto_core::request::HttpClient::new().unwrap(),
        creds,
        std::env::var("S3_ENDPOINT")
            .ok()
            .map(|e| Region::Custom {
                name: std::env::var("S3_REGION").unwrap_or_else(|_| "us-west-1".to_owned()),
                endpoint: e,
            })
            .unwrap_or(Region::UsWest1),
    ))
}
<|endoftext|>


<commit_before>use std::ptr::null_mut;
use std::os::unix::io::RawFd;
use libc::c_int;
use {Errno, Result};
use sys::time::TimeVal;

pub const FD_SETSIZE: RawFd = 1024;

#[cfg(any(target_os = "macos", target_os = "ios"))]
#[repr(C)]
pub struct FdSet {
    bits: [i32; FD_SETSIZE as usize / 32]
}

#[cfg(any(target_os = "macos", target_os = "ios"))]
const BITS: usize = 32;

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
#[repr(C)]
#[derive(Clone)]
pub struct FdSet {
    bits: [u64; FD_SETSIZE as usize / 64]
}

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
const BITS: usize = 64;

impl FdSet {
    pub fn new() -> FdSet {
        FdSet {
            bits: [0; FD_SETSIZE as usize / BITS]
        }
    }

    pub fn insert(&mut self, fd: RawFd) {
        let fd = fd as usize;
        self.bits[fd / BITS] |= 1 << (fd % BITS);
    }

    pub fn remove(&mut self, fd: RawFd) {
        let fd = fd as usize;
        self.bits[fd / BITS] &= !(1 << (fd % BITS));
    }

    pub fn contains(&mut self, fd: RawFd) -> bool {
        let fd = fd as usize;
        self.bits[fd / BITS] & (1 << (fd % BITS)) > 0
    }

    pub fn clear(&mut self) {
        for bits in &mut self.bits {
            *bits = 0
        }
    }
}

mod ffi {
    use libc::c_int;
    use sys::time::TimeVal;
    use super::FdSet;

    extern {
        pub fn select(nfds: c_int,
                      readfds: *mut FdSet,
                      writefds: *mut FdSet,
                      errorfds: *mut FdSet,
                      timeout: *mut TimeVal) -> c_int;
    }
}

pub fn select(nfds: c_int,
              readfds: Option<&mut FdSet>,
              writefds: Option<&mut FdSet>,
              errorfds: Option<&mut FdSet>,
              timeout: Option<&mut TimeVal>) -> Result<c_int> {
    let readfds = readfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let writefds = writefds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let errorfds = errorfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let timeout = timeout.map(|tv| tv as *mut TimeVal).unwrap_or(null_mut());

    let res = unsafe {
        ffi::select(nfds, readfds, writefds, errorfds, timeout)
    };

    Errno::result(res)
}
<commit_msg>Auto merge of #405 - murarth:clone-fdset, r=fiveop<commit_after>use std::ptr::null_mut;
use std::os::unix::io::RawFd;
use libc::c_int;
use {Errno, Result};
use sys::time::TimeVal;

pub const FD_SETSIZE: RawFd = 1024;

#[cfg(any(target_os = "macos", target_os = "ios"))]
#[repr(C)]
#[derive(Clone)]
pub struct FdSet {
    bits: [i32; FD_SETSIZE as usize / 32]
}

#[cfg(any(target_os = "macos", target_os = "ios"))]
const BITS: usize = 32;

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
#[repr(C)]
#[derive(Clone)]
pub struct FdSet {
    bits: [u64; FD_SETSIZE as usize / 64]
}

#[cfg(not(any(target_os = "macos", target_os = "ios")))]
const BITS: usize = 64;

impl FdSet {
    pub fn new() -> FdSet {
        FdSet {
            bits: [0; FD_SETSIZE as usize / BITS]
        }
    }

    pub fn insert(&mut self, fd: RawFd) {
        let fd = fd as usize;
        self.bits[fd / BITS] |= 1 << (fd % BITS);
    }

    pub fn remove(&mut self, fd: RawFd) {
        let fd = fd as usize;
        self.bits[fd / BITS] &= !(1 << (fd % BITS));
    }

    pub fn contains(&mut self, fd: RawFd) -> bool {
        let fd = fd as usize;
        self.bits[fd / BITS] & (1 << (fd % BITS)) > 0
    }

    pub fn clear(&mut self) {
        for bits in &mut self.bits {
            *bits = 0
        }
    }
}

mod ffi {
    use libc::c_int;
    use sys::time::TimeVal;
    use super::FdSet;

    extern {
        pub fn select(nfds: c_int,
                      readfds: *mut FdSet,
                      writefds: *mut FdSet,
                      errorfds: *mut FdSet,
                      timeout: *mut TimeVal) -> c_int;
    }
}

pub fn select(nfds: c_int,
              readfds: Option<&mut FdSet>,
              writefds: Option<&mut FdSet>,
              errorfds: Option<&mut FdSet>,
              timeout: Option<&mut TimeVal>) -> Result<c_int> {
    let readfds = readfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let writefds = writefds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let errorfds = errorfds.map(|set| set as *mut FdSet).unwrap_or(null_mut());
    let timeout = timeout.map(|tv| tv as *mut TimeVal).unwrap_or(null_mut());

    let res = unsafe {
        ffi::select(nfds, readfds, writefds, errorfds, timeout)
    };

    Errno::result(res)
}
<|endoftext|>


<commit_before>use error::PngError;
use miniz_sys;
use std::cmp::max;
use zopfli;

#[doc(hidden)]
pub mod miniz_stream;

/// Decompress a data stream using the DEFLATE algorithm
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
    let mut input = data.to_owned();
    let mut stream = miniz_stream::Stream::new_decompress();
    let mut output = Vec::with_capacity(data.len());
    loop {
        match stream.decompress_vec(input.as_mut(), output.as_mut()) {
            miniz_sys::MZ_OK => output.reserve(data.len()),
            miniz_sys::MZ_STREAM_END => break,
            c => return Err(PngError::new(&format!("Error code on decompress: {}", c))),
        }
    }
    output.shrink_to_fit();

    Ok(output)
}

/// Compress a data stream using the zlib implementation of the DEFLATE algorithm
pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u8) -> Result<Vec<u8>, PngError> {
    let mut input = data.to_owned();
    // Compressed input should be smaller than decompressed, so allocate less than data.len()
    // However, it needs a minimum capacity in order to handle very small images
    let mut output = Vec::with_capacity(max(1024, data.len() / 20));
    let mut stream = miniz_stream::Stream::new_compress(
        i32::from(zc),
        i32::from(zw),
        i32::from(zm),
        i32::from(zs),
    );
    loop {
        match stream.compress_vec(input.as_mut(), output.as_mut()) {
            miniz_sys::MZ_OK => output.reserve(max(1024, data.len() / 20)),
            miniz_sys::MZ_STREAM_END => break,
            c => return Err(PngError::new(&format!("Error code on compress: {}", c))),
        }
    }

    output.shrink_to_fit();

    Ok(output)
}

pub fn zopfli_deflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
    let mut output = Vec::with_capacity(max(1024, data.len() / 20));
    let options = zopfli::Options::default();
    match zopfli::compress(&options, &zopfli::Format::Zlib, data, &mut output) {
        Ok(_) => (),
        Err(_) => return Err(PngError::new("Failed to compress in zopfli")),
    };
    output.shrink_to_fit();
    Ok(output)
}

#[derive(Clone, Copy, Debug, PartialEq)]
/// DEFLATE algorithms supported by oxipng
pub enum Deflaters {
    /// Use the Zlib DEFLATE implementation
    Zlib,
    /// Use the better but slower Zopfli implementation
    Zopfli,
}
<commit_msg>Update some old documentation<commit_after>use error::PngError;
use miniz_sys;
use std::cmp::max;
use zopfli;

#[doc(hidden)]
pub mod miniz_stream;

/// Decompress a data stream using the DEFLATE algorithm
pub fn inflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
    let mut input = data.to_owned();
    let mut stream = miniz_stream::Stream::new_decompress();
    let mut output = Vec::with_capacity(data.len());
    loop {
        match stream.decompress_vec(input.as_mut(), output.as_mut()) {
            miniz_sys::MZ_OK => output.reserve(data.len()),
            miniz_sys::MZ_STREAM_END => break,
            c => return Err(PngError::new(&format!("Error code on decompress: {}", c))),
        }
    }
    output.shrink_to_fit();

    Ok(output)
}

/// Compress a data stream using the DEFLATE algorithm
pub fn deflate(data: &[u8], zc: u8, zm: u8, zs: u8, zw: u8) -> Result<Vec<u8>, PngError> {
    let mut input = data.to_owned();
    // Compressed input should be smaller than decompressed, so allocate less than data.len()
    // However, it needs a minimum capacity in order to handle very small images
    let mut output = Vec::with_capacity(max(1024, data.len() / 20));
    let mut stream = miniz_stream::Stream::new_compress(
        i32::from(zc),
        i32::from(zw),
        i32::from(zm),
        i32::from(zs),
    );
    loop {
        match stream.compress_vec(input.as_mut(), output.as_mut()) {
            miniz_sys::MZ_OK => output.reserve(max(1024, data.len() / 20)),
            miniz_sys::MZ_STREAM_END => break,
            c => return Err(PngError::new(&format!("Error code on compress: {}", c))),
        }
    }

    output.shrink_to_fit();

    Ok(output)
}

pub fn zopfli_deflate(data: &[u8]) -> Result<Vec<u8>, PngError> {
    let mut output = Vec::with_capacity(max(1024, data.len() / 20));
    let options = zopfli::Options::default();
    match zopfli::compress(&options, &zopfli::Format::Zlib, data, &mut output) {
        Ok(_) => (),
        Err(_) => return Err(PngError::new("Failed to compress in zopfli")),
    };
    output.shrink_to_fit();
    Ok(output)
}

#[derive(Clone, Copy, Debug, PartialEq)]
/// DEFLATE algorithms supported by oxipng
pub enum Deflaters {
    /// Use the Zlib/Miniz DEFLATE implementation
    Zlib,
    /// Use the better but slower Zopfli implementation
    Zopfli,
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::io::Read;
use std::fs::{File};
use std::path::Path;

use base64;
use failure::{Error, ResultExt};
use ssh_keys::{PrivateKey, openssh};

use lithos::nacl;
use lithos::sandbox_config::SandboxConfig;

fn parse_private_key(filename: &Path) -> Result<Vec<PrivateKey>, Error> {
    let mut buf = String::with_capacity(1024);
    File::open(filename)
        .and_then(|mut f| f.read_to_string(&mut buf))
        .context(Path::new(filename).display().to_string())?;
    Ok(openssh::parse_private_key(&buf)?)
}

fn decrypt(key: &PrivateKey, value: &str) -> Result<String, Error> {
    let key_bytes = match *key {
        PrivateKey::Ed25519(key) => key,
        _ => bail!("Only ed25519 keys are supported"),
    };
    if !value.starts_with("v1:") {
        bail!("Only v1 secrets are supported");
    }
    let data = base64::decode(&value["v1:".len()..])?;
    if data.len() < 32+24 {
        bail!("data is too short");
    }
    let plain = nacl::crypto_secretbox_open(
        &data[32+24..], &data[32..32+24], &key_bytes[32..])
        .map_err(|e| format_err!("{}", e))?;
    String::from_utf8(plain)
        .map_err(|_| format_err!("Can't decode secret as utf-8"))
}

fn decrypt_pair(keys: &[PrivateKey], values: &[String])
    -> Result<String, Vec<Error>>
{
    let mut errs = Vec::new();
    for key in keys {
        for value in values {
            match decrypt(key, value) {
                Ok(value) => return Ok(value),
                Err(e) => errs.push(e),
            }
        }
    }
    Err(errs)
}

pub fn decode(sandbox: &SandboxConfig, secrets: &BTreeMap<String, Vec<String>>)
    -> Result<BTreeMap<String, String>, Error>
{
    if secrets.len() == 0 {
        // do not read keys
        return Ok(BTreeMap::new());
    }

    let keys = if let Some(ref filename) = sandbox.secrets_private_key {
        parse_private_key(&filename)?
    } else {
        bail!("No secrets key file defined to decode secrets: {:?}",
            secrets.keys());
    };

    let mut res = BTreeMap::new();

    for (name, values) in secrets {
        res.insert(name.clone(), decrypt_pair(&keys, values)
            .map_err(|e| {
                format_err!("Can't decrypt secret {:?}, errors: {}", name,
                    e.iter().map(|x| x.to_string())
                    .collect::<Vec<_>>().join(", "))
            })?);
    }

    Ok(res)
}
<commit_msg>Check permissions of the private key file<commit_after>use std::collections::BTreeMap;
use std::io::Read;
use std::fs::{File};
use std::os::unix::fs::MetadataExt;
use std::path::Path;

use base64;
use failure::{Error, ResultExt};
use ssh_keys::{PrivateKey, openssh};

use lithos::nacl;
use lithos::sandbox_config::SandboxConfig;

fn parse_private_key(filename: &Path) -> Result<Vec<PrivateKey>, Error> {
    let mut buf = String::with_capacity(1024);
    let f = File::open(filename)?
        .context(Path::new(filename).display().to_string())?;
    let meta = f.metadata()?
        .context(Path::new(filename).display().to_string())?;
    if f.uid() != 0 {
        bail!("Key must be owned by root");
    }
    if f.mode() & 0o777 & !0o600 != 0 {
        bail!("Key's mode must be 0600");
    }
    f.read_to_string(&mut buf)?;
        .context(Path::new(filename).display().to_string())?;
    Ok(openssh::parse_private_key(&buf)?)
}

fn decrypt(key: &PrivateKey, value: &str) -> Result<String, Error> {
    let key_bytes = match *key {
        PrivateKey::Ed25519(key) => key,
        _ => bail!("Only ed25519 keys are supported"),
    };
    if !value.starts_with("v1:") {
        bail!("Only v1 secrets are supported");
    }
    let data = base64::decode(&value["v1:".len()..])?;
    if data.len() < 32+24 {
        bail!("data is too short");
    }
    let plain = nacl::crypto_secretbox_open(
        &data[32+24..], &data[32..32+24], &key_bytes[32..])
        .map_err(|e| format_err!("{}", e))?;
    String::from_utf8(plain)
        .map_err(|_| format_err!("Can't decode secret as utf-8"))
}

fn decrypt_pair(keys: &[PrivateKey], values: &[String])
    -> Result<String, Vec<Error>>
{
    let mut errs = Vec::new();
    for key in keys {
        for value in values {
            match decrypt(key, value) {
                Ok(value) => return Ok(value),
                Err(e) => errs.push(e),
            }
        }
    }
    Err(errs)
}

pub fn decode(sandbox: &SandboxConfig, secrets: &BTreeMap<String, Vec<String>>)
    -> Result<BTreeMap<String, String>, Error>
{
    if secrets.len() == 0 {
        // do not read keys
        return Ok(BTreeMap::new());
    }

    let keys = if let Some(ref filename) = sandbox.secrets_private_key {
        parse_private_key(&filename)?
    } else {
        bail!("No secrets key file defined to decode secrets: {:?}",
            secrets.keys());
    };

    let mut res = BTreeMap::new();

    for (name, values) in secrets {
        res.insert(name.clone(), decrypt_pair(&keys, values)
            .map_err(|e| {
                format_err!("Can't decrypt secret {:?}, errors: {}", name,
                    e.iter().map(|x| x.to_string())
                    .collect::<Vec<_>>().join(", "))
            })?);
    }

    Ok(res)
}
<|endoftext|>


<commit_before>use std::cmp::min;
use std::ops::{Index, IndexMut};
use std::str::Chars;

/// A line in a buffer.
pub trait Line<'a> {
    /// The underlying iterator.
    type Iter: Iterator<Item = char> + 'a;

    /// Iterator over characters.
    fn chars_iter(&'a self) -> Self::Iter;
}

impl<'a, T: AsRef<str>> Line<'a> for T {
    type Iter = Chars<'a>;

    fn chars_iter(&self) -> Chars {
        self.as_ref().chars()
    }
}

/// A buffer structure
pub trait TextBuffer<'a> {
    /// The line type of the buffer.
    type Line: 'a + Line<'a>;
    /// The line iterator.
    type LineIter: Iterator<Item = &'a Self::Line>;

    /// Create a new empty split buffer
    fn new() -> Self;

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self;

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&Self::Line>;

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut Self::Line>;

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> Self::Line;

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: Self::Line);

    /// Convert a vector of lines to a split buffer
    fn from_lines(vec: &[Self::Line]) -> SplitBuffer;

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). X value.
    fn focus_hint_x(&mut self, x: usize);

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). Y value.
    fn focus_hint_y(&mut self, y: usize);

    /// Get the number of lines in the buffer.
    fn len(&self) -> usize;

    /// Get an iterator over the lines in the buffer.
    fn lines(&'a self) -> Self::LineIter;

    /// Get an iterator over the line starting from a certain line
    fn lines_from(&'a self, from: usize) -> Self::LineIter;

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str;
}

/// The buffer data structure, that Sodium is using.
///
/// This structure consists of two "subbuffers", which are just vectors over lines (defined by
/// Strings). The split is called a center.
///
/// The nearer a given operation is to the center, the better it performs.
///
/// The second buffer is in reverse order to get the particular efficiency we want.
pub struct SplitBuffer {
    before: Vec<String>,
    after: Vec<String>,
    #[cfg(debug)] _hinted_since_edit: bool,
}

impl SplitBuffer {
    fn up(&mut self) {
        self.after
            .push(self.before.pop().expect("Popped last element"));
    }

    fn down(&mut self) {
        self.before
            .push(self.after.pop().expect("Popped last element"));
    }

    fn y(&self) -> usize {
        self.before.len()
    }
}

// TODO remove
impl SplitBuffer {
    /// Convert the buffer to a string.
    pub fn to_string(&self) -> String {
        self.lines().map(|x| x.to_owned() + "\n").collect()
    }
}

impl<'a> TextBuffer<'a> for SplitBuffer {
    type Line = String;
    type LineIter = SplitBufIter<'a>;

    fn new() -> Self {
        SplitBuffer {
            before: vec![String::new()],
            after: Vec::new(),
        }
    }

    fn from_str(s: &str) -> Self {
        SplitBuffer {
            before: s.lines().map(ToOwned::to_owned).collect(),
            after: Vec::new(),
        }
    }

    fn get_line(&self, n: usize) -> Option<&String> {
        if n < self.before.len() {
            Some(&self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&self.after[n])
        } else {
            None
        }
    }

    fn get_line_mut(&mut self, n: usize) -> Option<&mut String> {
        if n < self.before.len() {
            Some(&mut self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&mut self.after[n])
        } else {
            None
        }
    }

    fn remove_line(&mut self, n: usize) -> String {
        if n < self.before.len() {
            self.before.remove(n)
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            let ret = self.after.remove(n);
            if n == 0 {
                self.up();
            }
            ret
        } else {
            panic!("Out of bound");
        }
    }

    fn insert_line(&mut self, n: usize, line: String) {
        if n < self.before.len() {
            self.before.insert(n, line);
        } else if n <= self.len() {
            let n = self.len() - n;
            self.after.insert(n, line);
        } else {
            panic!("Out of bound");
        }
    }

    fn from_lines(ln: &[String]) -> SplitBuffer {
        SplitBuffer {
            before: ln.to_owned(),
            after: Vec::new(),
        }
    }

    fn focus_hint_y(&mut self, y: usize) {
        if y < self.y() {
            for _ in 0..min(self.y() - y, self.before.len()) {
                self.up();
            }
        } else if y > self.y() {
            for _ in 0..min(y - self.y(), self.after.len()) {
                self.down();
            }
        } else if y >= self.len() {
            panic!("Out of bound");
        }
    }

    fn focus_hint_x(&mut self, _: usize) {}

    fn len(&self) -> usize {
        self.before.len() + self.after.len()
    }

    fn lines(&'a self) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: 0,
        }
    }

    fn lines_from(&'a self, from: usize) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: from,
        }
    }

    fn get_indent(&self, n: usize) -> &str {
        if let Some(ln) = self.get_line(n) {
            let mut len = 0;
            for c in ln.chars() {
                match c {
                    '\t' | ' ' => len += 1,
                    _ => break,
                }
            }
            &ln[..len]
        } else {
            ""
        }
    }
}

impl Index<usize> for SplitBuffer {
    type Output = String;

    fn index<'a>(&'a self, index: usize) -> &'a String {
        self.get_line(index).expect("Out of bound")
    }
}
impl IndexMut<usize> for SplitBuffer {
    fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut String {
        #[cfg(debug)]
        fn debug_check(b: &mut SplitBuffer) {
            if b._hinted_since_edit {
                b._hinted_since_edit = false;
            } else {
                panic!("No focus hint given since last edit!");
            }
        }

        #[cfg(not(debug))]
        fn debug_check(_: &mut SplitBuffer) {}

        debug_check(&mut *self);

        self.get_line_mut(index).expect("Out of bound")
    }
}

/// A iterator over the lines of a split buffer
pub struct SplitBufIter<'a> {
    buffer: &'a SplitBuffer,
    line: usize,
}

impl<'a> Iterator for SplitBufIter<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<&'a String> {
        self.nth(1)
    }

    fn nth(&mut self, n: usize) -> Option<&'a String> {
        let res = self.buffer.get_line(self.line);
        self.line += n;

        res
    }

    fn count(self) -> usize {
        self.buffer.len()
    }
}

impl<'a> DoubleEndedIterator for SplitBufIter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        if self.line == 0 {
            None
        } else {
            self.line -= 1;
            self.buffer.get_line(self.line)
        }
    }
}
<commit_msg>Fix bug that could cause panic when deleting lines<commit_after>use std::cmp::min;
use std::ops::{Index, IndexMut};
use std::str::Chars;

/// A line in a buffer.
pub trait Line<'a> {
    /// The underlying iterator.
    type Iter: Iterator<Item = char> + 'a;

    /// Iterator over characters.
    fn chars_iter(&'a self) -> Self::Iter;
}

impl<'a, T: AsRef<str>> Line<'a> for T {
    type Iter = Chars<'a>;

    fn chars_iter(&self) -> Chars {
        self.as_ref().chars()
    }
}

/// A buffer structure
pub trait TextBuffer<'a> {
    /// The line type of the buffer.
    type Line: 'a + Line<'a>;
    /// The line iterator.
    type LineIter: Iterator<Item = &'a Self::Line>;

    /// Create a new empty split buffer
    fn new() -> Self;

    /// Convert a string to a split buffer
    fn from_str(s: &str) -> Self;

    /// Get the nth line in the buffer by option reference
    fn get_line(&self, n: usize) -> Option<&Self::Line>;

    /// Get the nth line in the buffer by optional mutable reference
    fn get_line_mut(&mut self, n: usize) -> Option<&mut Self::Line>;

    /// Remove the nth line and return it. Panics on out of bound.
    fn remove_line(&mut self, n: usize) -> Self::Line;

    /// Insert line at n. Panics on out of bound.
    fn insert_line(&mut self, n: usize, line: Self::Line);

    /// Convert a vector of lines to a split buffer
    fn from_lines(vec: &[Self::Line]) -> SplitBuffer;

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). X value.
    fn focus_hint_x(&mut self, x: usize);

    /// Give a hint on where the operations are most frequent (i.e. where the cursor is). Y value.
    fn focus_hint_y(&mut self, y: usize);

    /// Get the number of lines in the buffer.
    fn len(&self) -> usize;

    /// Get an iterator over the lines in the buffer.
    fn lines(&'a self) -> Self::LineIter;

    /// Get an iterator over the line starting from a certain line
    fn lines_from(&'a self, from: usize) -> Self::LineIter;

    /// Get the leading whitespaces of the nth line. Used for autoindenting.
    fn get_indent(&self, n: usize) -> &str;
}

/// The buffer data structure, that Sodium is using.
///
/// This structure consists of two "subbuffers", which are just vectors over lines (defined by
/// Strings). The split is called a center.
///
/// The nearer a given operation is to the center, the better it performs.
///
/// The second buffer is in reverse order to get the particular efficiency we want.
pub struct SplitBuffer {
    before: Vec<String>,
    after: Vec<String>,
    #[cfg(debug)] _hinted_since_edit: bool,
}

impl SplitBuffer {
    fn up(&mut self) {
        self.after
            .push(self.before.pop().expect("Popped last element"));
    }

    fn down(&mut self) {
        self.before
            .push(self.after.pop().expect("Popped last element"));
    }

    fn y(&self) -> usize {
        self.before.len()
    }
}

// TODO remove
impl SplitBuffer {
    /// Convert the buffer to a string.
    pub fn to_string(&self) -> String {
        self.lines().map(|x| x.to_owned() + "\n").collect()
    }
}

impl<'a> TextBuffer<'a> for SplitBuffer {
    type Line = String;
    type LineIter = SplitBufIter<'a>;

    fn new() -> Self {
        SplitBuffer {
            before: vec![String::new()],
            after: Vec::new(),
        }
    }

    fn from_str(s: &str) -> Self {
        SplitBuffer {
            before: s.lines().map(ToOwned::to_owned).collect(),
            after: Vec::new(),
        }
    }

    fn get_line(&self, n: usize) -> Option<&String> {
        if n < self.before.len() {
            Some(&self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&self.after[n])
        } else {
            None
        }
    }

    fn get_line_mut(&mut self, n: usize) -> Option<&mut String> {
        if n < self.before.len() {
            Some(&mut self.before[n])
        } else if n < self.len() {
            let n = self.len() - 1 - n;
            Some(&mut self.after[n])
        } else {
            None
        }
    }

    fn remove_line(&mut self, n: usize) -> String {
        if n < self.before.len() {
            self.before.remove(n)
        } else if n < self.len() {
            let n = n - self.before.len();
            let ret = self.after.remove(n);
            if n == 0 {
                self.up();
            }
            ret
        } else {
            panic!("Out of bound");
        }
    }

    fn insert_line(&mut self, n: usize, line: String) {
        if n < self.before.len() {
            self.before.insert(n, line);
        } else if n <= self.len() {
            let n = self.len() - n;
            self.after.insert(n, line);
        } else {
            panic!("Out of bound");
        }
    }

    fn from_lines(ln: &[String]) -> SplitBuffer {
        SplitBuffer {
            before: ln.to_owned(),
            after: Vec::new(),
        }
    }

    fn focus_hint_y(&mut self, y: usize) {
        if y < self.y() {
            for _ in 0..min(self.y() - y, self.before.len()) {
                self.up();
            }
        } else if y > self.y() {
            for _ in 0..min(y - self.y(), self.after.len()) {
                self.down();
            }
        } else if y >= self.len() {
            panic!("Out of bound");
        }
    }

    fn focus_hint_x(&mut self, _: usize) {}

    fn len(&self) -> usize {
        self.before.len() + self.after.len()
    }

    fn lines(&'a self) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: 0,
        }
    }

    fn lines_from(&'a self, from: usize) -> SplitBufIter<'a> {
        SplitBufIter {
            buffer: self,
            line: from,
        }
    }

    fn get_indent(&self, n: usize) -> &str {
        if let Some(ln) = self.get_line(n) {
            let mut len = 0;
            for c in ln.chars() {
                match c {
                    '\t' | ' ' => len += 1,
                    _ => break,
                }
            }
            &ln[..len]
        } else {
            ""
        }
    }
}

impl Index<usize> for SplitBuffer {
    type Output = String;

    fn index<'a>(&'a self, index: usize) -> &'a String {
        self.get_line(index).expect("Out of bound")
    }
}
impl IndexMut<usize> for SplitBuffer {
    fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut String {
        #[cfg(debug)]
        fn debug_check(b: &mut SplitBuffer) {
            if b._hinted_since_edit {
                b._hinted_since_edit = false;
            } else {
                panic!("No focus hint given since last edit!");
            }
        }

        #[cfg(not(debug))]
        fn debug_check(_: &mut SplitBuffer) {}

        debug_check(&mut *self);

        self.get_line_mut(index).expect("Out of bound")
    }
}

/// A iterator over the lines of a split buffer
pub struct SplitBufIter<'a> {
    buffer: &'a SplitBuffer,
    line: usize,
}

impl<'a> Iterator for SplitBufIter<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<&'a String> {
        self.nth(1)
    }

    fn nth(&mut self, n: usize) -> Option<&'a String> {
        let res = self.buffer.get_line(self.line);
        self.line += n;

        res
    }

    fn count(self) -> usize {
        self.buffer.len()
    }
}

impl<'a> DoubleEndedIterator for SplitBufIter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        if self.line == 0 {
            None
        } else {
            self.line -= 1;
            self.buffer.get_line(self.line)
        }
    }
}
<|endoftext|>


<commit_before>pub trait Environment {
  fn reward(&self) -> f64;
  fn observation(&self) -> i16;

  fn update(&mut self, action: i16);
}

pub struct CoinFlip {
  last_toss: bool,
}

impl CoinFlip {
  pub fn new() -> CoinFlip {
    CoinFlip {
      last_toss: false,  // tails
    }
  }
}

//impl Environment for CoinFlip {
//}
<commit_msg>Implement the Environment trait for CoinFlip.<commit_after>pub trait Environment {
  fn reward(&self) -> f64;
  fn observation(&self) -> i16;

  fn update(&mut self, action: i16);
}

pub struct CoinFlip {
  last_toss: bool,
  last_guess: bool,
}

impl CoinFlip {
  pub fn new() -> CoinFlip {
    CoinFlip {
      last_toss: false,   // tails

      // TODO make this optional.
      last_guess: false,  // tails
    }
  }
}

impl Environment for CoinFlip {
  fn reward(&self) -> f64 {
    return if self.last_guess == self.last_toss { 1.0 } else { 0.0 };
  }

  fn observation(&self) -> i16 {
    return self.last_toss as i16;
  }

  fn update(&mut self, action: i16) {
    self.last_toss = !self.last_toss;  // just flip over for now
  }
}
<|endoftext|>


<commit_before>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
*/

use term_painter::{Style, ToStyle};
use term_painter::Attr::{Plain, Bold};
use term_painter::Color::{Red, Cyan, Yellow, Green, Blue};

use esr_errors::*;

#[derive(Clone)]
pub struct EsrFormatter {
    style: Style,
    text: String,
    trail: String,
}

impl EsrFormatter {
    pub fn new(style: Style, text: &str, trail: &str) -> Self {
        Self {
            style,
            text: String::from(text),
            trail: String::from(trail),
        }
    }

    pub fn trail_only(trail: &str) -> Self {
        Self {
            style: Plain.to_style(),
            text: String::new(),
            trail: String::from(trail),
        }
    }

    pub fn empty() -> Self {
        Self {
            style: Plain.to_style(),
            text: String::new(),
            trail: String::new(),
        }
    }

    pub fn print(&self, formatted: bool) {
        if formatted {
            print!("{}{}", self.style.paint(&self.text), self.trail);
        } else {
            print!("{}{}", &self.text, self.trail);
        }
    }

    pub fn print_grp(grp: &[Self], formatted: bool) {
        for f in grp {
            f.print(formatted);
        }
        println!("");
    }
}

pub struct EsrPrinter;

impl EsrPrinter {
    pub fn msg_pair(msg: &str, val: &str) -> [EsrFormatter; 2] {
        [
            EsrFormatter::new(Cyan.bold(), msg, ": "),
            EsrFormatter::new(Bold.to_style(), val, "\n "),
        ]
    }

    pub fn msg_pair_complex(msg: &str, val: &[EsrFormatter]) -> Vec<EsrFormatter> {
        let mut ret = Vec::with_capacity(val.len() + 1);
        ret.push(EsrFormatter::new(Cyan.bold(), msg, ": "));
        ret.extend_from_slice(val);
        ret
    }

    pub fn id(id: &str) -> EsrFormatter {
        EsrFormatter::new(Blue.bold(), id, " ")
    }

    pub fn err(val: &str) -> EsrFormatter {
        EsrFormatter::new(Red.bold(), val, "\n")
    }

    pub fn all_yanked() -> EsrFormatter {
        EsrFormatter::new(Red.bold(), "(empty/all yanked)", "\n ")
    }

    pub fn desc(orig_desc: &str) -> String {
        let desc = String::from(orig_desc);

        // Replace white-space with a single space
        let mut tmp = desc.replace(&['\t', '\n'][..], " ");
        let fixed_ws = loop {
            let curr = tmp.replace("  ", " ");
            if curr == tmp {
                break curr.trim().to_string();
            }
            tmp = curr;
        };

        // Multi-line
        let mut last_n = fixed_ws.rfind('\n').unwrap_or(0);
        let mut multi_line = fixed_ws;

        while multi_line.len() - last_n > 64 {
            let new_n = multi_line
                .get(last_n+1..last_n+64)
                .and_then(|slice| slice.rfind(' '))
                .unwrap_or(last_n);

            if new_n != last_n {
                assert!(last_n+new_n+1 < multi_line.len());
                unsafe { multi_line.as_mut_vec()[last_n+new_n+1] = b'\0' };
                multi_line = multi_line.replace('\0', "\n              ");
                last_n += new_n + " Discription: ".len();
            }
            else {
                break;
            }
        }

        multi_line
    }

    pub fn release(ver_opt: Option<&str>, age_res_opt: Option<Result<f64>>) -> String {
        match (ver_opt, age_res_opt) {
            (Some(ver), Some(Ok(age))) => format!("{} (released {:.1} months ago)", ver, age),
            _ => "N/A".into(),
        }
    }

    pub fn releases(stable: usize, non_yanked_pre: usize, yanked: usize) -> [EsrFormatter; 3] {
        [
            EsrFormatter::new(Green.bold(), &format!("{}", stable), "+"),
            EsrFormatter::new(Yellow.bold(), &format!("{}", non_yanked_pre), "+"),
            EsrFormatter::new(Red.bold(), &format!("{}", yanked), "\n "),
        ]
    }

    pub fn score_error(msg: &str) -> [EsrFormatter; 2] {
        [
            EsrFormatter::new(Red.bold(), msg, ": "),
            EsrFormatter::new(Bold.to_style(), "Error", "\n "),
        ]
    }

    pub fn score_na(msg: &str) -> [EsrFormatter; 2] {
        Self::msg_pair(msg, "N/A")
    }

    pub fn score_overview(msg: &str, pos: f64, neg: f64) -> [EsrFormatter; 4] {
        [
            EsrFormatter::new(Cyan.bold(), msg, ": "),
            EsrFormatter::new(Yellow.bold(), &format!("{:.3}", pos + neg) , " ("),
            EsrFormatter::new(Green.bold(), &format!("+{:.3}", pos) , " / "),
            EsrFormatter::new(Red.bold(), &format!("{:.3}", neg) , ")\n "),
        ]
    }

    pub fn score_details(msg: &str, table: &[(String, String, String)]) -> Vec<EsrFormatter> {
        let msg = format!("{: ^49}", msg);
        let frame = format!("{: ^49}", "-".repeat(msg.len()));

        let mut score_formatted = Vec::with_capacity(4096);
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*frame, "\n"));
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*msg, "\n"));
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*frame, "\n"));

        for line in table {
            if line.1.find("* -").is_some() {
                score_formatted.push(EsrFormatter::new(Yellow.bold(), &*line.0, " | "));
                score_formatted.push(EsrFormatter::new(Red.bold(), &*line.1, " | "));
                score_formatted.push(EsrFormatter::new(Red.bold(), &*line.2, "\n"));
            } else {
                score_formatted.push(EsrFormatter::new(Yellow.bold(), &*line.0, " | "));
                score_formatted.push(EsrFormatter::new(Green.bold(), &*line.1, " | "));
                score_formatted.push(EsrFormatter::new(Green.bold(), &*("+".to_string() + &*line.2), "\n"));
            }
        }
        score_formatted
    }

    pub fn crate_no_score(id: &str, e: &Error) {
        let msg = format!("Failed to get scores for crate \"{}\": {}.", id, e);
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn repo_no_score(repo: &str, e: &Error) {
        let msg = format!("Failed to get scores for repo \"{}\": {}.", repo, e);
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn search_no_results(search_pattern: &str) {
        let msg = format!("Searching for \"{}\" returned no results.", search_pattern);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn search_failed(search_pattern: &str, e: &Error) {
        let msg = format!("Search for \"{}\" failed.\n{}", search_pattern, backtrace_msg!(e));
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn limit_out_of_range(limit: usize, min: usize, max: usize) {
        let msg = format!("{} is out of the range of valid limits. \
                          Please pass a value between {} and {}.", limit, min, max);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn limit_invalid(limit: &str) {
        let msg = format!("\"{}\" is an invalid limit value.", limit);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn no_token() {
        let msg = "Accessing GitHub's API wothout hitting rate-limits requires providing an access\
                   token.\n\n\
                   You can pass a token via -g/--gh-token option.\n\
                   Or by setting the variable CARGO_ESR_GH_TOKEN in the environment.\n\n\
                   To a acquire an access token, visit: <https://github.com/settings/tokens/new>\n\n\
                   Alternatively, you can pass -o/--crate-only to skip getting repository info.";
        println!("{}", Yellow.bold().paint(msg));
    }
}
<commit_msg>esr_printer: Improve printed error messages a little bit<commit_after>/*
    This file is a part of cargo-esr.

    Copyright (C) 2017 Mohammad AlSaleh <CE.Mohammad.AlSaleh at gmail.com>
    https://github.com/rust-alt/cargo-esr

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at <http://mozilla.org/MPL/2.0/>.
*/

use term_painter::{Style, ToStyle};
use term_painter::Attr::{Plain, Bold};
use term_painter::Color::{Red, Cyan, Yellow, Green, Blue};

use esr_errors::*;

#[derive(Clone)]
pub struct EsrFormatter {
    style: Style,
    text: String,
    trail: String,
}

impl EsrFormatter {
    pub fn new(style: Style, text: &str, trail: &str) -> Self {
        Self {
            style,
            text: String::from(text),
            trail: String::from(trail),
        }
    }

    pub fn trail_only(trail: &str) -> Self {
        Self {
            style: Plain.to_style(),
            text: String::new(),
            trail: String::from(trail),
        }
    }

    pub fn empty() -> Self {
        Self {
            style: Plain.to_style(),
            text: String::new(),
            trail: String::new(),
        }
    }

    pub fn print(&self, formatted: bool) {
        if formatted {
            print!("{}{}", self.style.paint(&self.text), self.trail);
        } else {
            print!("{}{}", &self.text, self.trail);
        }
    }

    pub fn print_grp(grp: &[Self], formatted: bool) {
        for f in grp {
            f.print(formatted);
        }
        println!("");
    }
}

pub struct EsrPrinter;

impl EsrPrinter {
    pub fn msg_pair(msg: &str, val: &str) -> [EsrFormatter; 2] {
        [
            EsrFormatter::new(Cyan.bold(), msg, ": "),
            EsrFormatter::new(Bold.to_style(), val, "\n "),
        ]
    }

    pub fn msg_pair_complex(msg: &str, val: &[EsrFormatter]) -> Vec<EsrFormatter> {
        let mut ret = Vec::with_capacity(val.len() + 1);
        ret.push(EsrFormatter::new(Cyan.bold(), msg, ": "));
        ret.extend_from_slice(val);
        ret
    }

    pub fn id(id: &str) -> EsrFormatter {
        EsrFormatter::new(Blue.bold(), id, " ")
    }

    pub fn err(val: &str) -> EsrFormatter {
        EsrFormatter::new(Red.bold(), val, "\n")
    }

    pub fn all_yanked() -> EsrFormatter {
        EsrFormatter::new(Red.bold(), "(empty/all yanked)", "\n ")
    }

    pub fn desc(orig_desc: &str) -> String {
        let desc = String::from(orig_desc);

        // Replace white-space with a single space
        let mut tmp = desc.replace(&['\t', '\n'][..], " ");
        let fixed_ws = loop {
            let curr = tmp.replace("  ", " ");
            if curr == tmp {
                break curr.trim().to_string();
            }
            tmp = curr;
        };

        // Multi-line
        let mut last_n = fixed_ws.rfind('\n').unwrap_or(0);
        let mut multi_line = fixed_ws;

        while multi_line.len() - last_n > 64 {
            let new_n = multi_line
                .get(last_n+1..last_n+64)
                .and_then(|slice| slice.rfind(' '))
                .unwrap_or(last_n);

            if new_n != last_n {
                assert!(last_n+new_n+1 < multi_line.len());
                unsafe { multi_line.as_mut_vec()[last_n+new_n+1] = b'\0' };
                multi_line = multi_line.replace('\0', "\n              ");
                last_n += new_n + " Discription: ".len();
            }
            else {
                break;
            }
        }

        multi_line
    }

    pub fn release(ver_opt: Option<&str>, age_res_opt: Option<Result<f64>>) -> String {
        match (ver_opt, age_res_opt) {
            (Some(ver), Some(Ok(age))) => format!("{} (released {:.1} months ago)", ver, age),
            _ => "N/A".into(),
        }
    }

    pub fn releases(stable: usize, non_yanked_pre: usize, yanked: usize) -> [EsrFormatter; 3] {
        [
            EsrFormatter::new(Green.bold(), &format!("{}", stable), "+"),
            EsrFormatter::new(Yellow.bold(), &format!("{}", non_yanked_pre), "+"),
            EsrFormatter::new(Red.bold(), &format!("{}", yanked), "\n "),
        ]
    }

    pub fn score_error(msg: &str) -> [EsrFormatter; 2] {
        [
            EsrFormatter::new(Red.bold(), msg, ": "),
            EsrFormatter::new(Bold.to_style(), "Error", "\n "),
        ]
    }

    pub fn score_na(msg: &str) -> [EsrFormatter; 2] {
        Self::msg_pair(msg, "N/A")
    }

    pub fn score_overview(msg: &str, pos: f64, neg: f64) -> [EsrFormatter; 4] {
        [
            EsrFormatter::new(Cyan.bold(), msg, ": "),
            EsrFormatter::new(Yellow.bold(), &format!("{:.3}", pos + neg) , " ("),
            EsrFormatter::new(Green.bold(), &format!("+{:.3}", pos) , " / "),
            EsrFormatter::new(Red.bold(), &format!("{:.3}", neg) , ")\n "),
        ]
    }

    pub fn score_details(msg: &str, table: &[(String, String, String)]) -> Vec<EsrFormatter> {
        let msg = format!("{: ^49}", msg);
        let frame = format!("{: ^49}", "-".repeat(msg.len()));

        let mut score_formatted = Vec::with_capacity(4096);
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*frame, "\n"));
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*msg, "\n"));
        score_formatted.push(EsrFormatter::new(Cyan.bold(), &*frame, "\n"));

        for line in table {
            if line.1.find("* -").is_some() {
                score_formatted.push(EsrFormatter::new(Yellow.bold(), &*line.0, " | "));
                score_formatted.push(EsrFormatter::new(Red.bold(), &*line.1, " | "));
                score_formatted.push(EsrFormatter::new(Red.bold(), &*line.2, "\n"));
            } else {
                score_formatted.push(EsrFormatter::new(Yellow.bold(), &*line.0, " | "));
                score_formatted.push(EsrFormatter::new(Green.bold(), &*line.1, " | "));
                score_formatted.push(EsrFormatter::new(Green.bold(), &*("+".to_string() + &*line.2), "\n"));
            }
        }
        score_formatted
    }

    pub fn crate_no_score(id: &str, e: &Error) {
        let msg = format!("Error: {}.\nFailed to get scores for crate \"{}\". Maybe it does not exist.", e, id);
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn repo_no_score(repo: &str, e: &Error) {
        let msg = format!("Error: {}.\nFailed to get scores for repo \"{}\". Maybe it does not exist.", e, repo);
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn search_no_results(search_pattern: &str) {
        let msg = format!("Searching for \"{}\" returned no results.", search_pattern);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn search_failed(search_pattern: &str, e: &Error) {
        let msg = format!("Search for \"{}\" failed.\n{}", search_pattern, backtrace_msg!(e));
        println!("{}", Red.bold().paint(&msg));
    }

    pub fn limit_out_of_range(limit: usize, min: usize, max: usize) {
        let msg = format!("{} is out of the range of valid limits. \
                          Please pass a value between {} and {}.", limit, min, max);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn limit_invalid(limit: &str) {
        let msg = format!("\"{}\" is an invalid limit value.", limit);
        println!("{}", Yellow.bold().paint(&msg));
    }

    pub fn no_token() {
        let msg = "Accessing GitHub's API wothout hitting rate-limits requires providing an access\
                   token.\n\n\
                   You can pass a token via -g/--gh-token option.\n\
                   Or by setting the variable CARGO_ESR_GH_TOKEN in the environment.\n\n\
                   To a acquire an access token, visit: <https://github.com/settings/tokens/new>\n\n\
                   Alternatively, you can pass -o/--crate-only to skip getting repository info.";
        println!("{}", Yellow.bold().paint(msg));
    }
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
use rustc_serialize::json;

#[derive(RustcEncodable)]
pub struct Location {
    address: String,
    lat: f64,
    lon: f64,
}

#[derive(RustcEncodable)]
pub struct State {
    open:       bool,
    message:    String,
}

#[derive(RustcEncodable)]
pub struct Contact {
    irc:        String,
    twitter:    String,
    foursquare: String,
    email:      String,
}

#[derive(RustcEncodable)]
pub struct Status {
    api: String,
    space: String,
    logo: String,
    url: String,
    location: Location,

    state: State,
    contact: Contact,
    issue_report_channels: [&'static str; 2],

    projects: [&'static str; 3],
}

fn main() {

    let status = Status {
        api:    "0.13".to_string(),
        space:  "coredump".to_string(),
        logo:   "https://www.coredump.ch/logo.png".to_string(),
        url:    "https://www.coredump.ch/".to_string(),
        location: Location {
            address:    "Spinnereistrasse 2, 8640 Rapperswil, Switzerland".to_string(),
            lat:        47.22936,
            lon:        8.82949,
        },
        state: State {
            open: false,
            message: "Open every Monday from 20:00".to_string(),
        },
        contact: Contact {
            irc:        "irc://freenode.net/#coredump".to_string(),
            twitter:    "@coredump_ch".to_string(),
            foursquare: "525c20e5498e875d8231b1e5".to_string(),
            email:      "danilo@coredump.ch".to_string(),
        },
        issue_report_channels: ["email", "twitter"],
        projects: [
            "https://www.coredump.ch/projekte/",
            "https://discourse.coredump.ch/c/projects",
            "https://github.com/coredump-ch/"
        ]
    };
    let encoded = json::encode(&status).unwrap();
    println!("{}", encoded);
}
<commit_msg>Consistent indentation<commit_after>extern crate rustc_serialize;
use rustc_serialize::json;

#[derive(RustcEncodable)]
pub struct Location {
    address: String,
    lat: f64,
    lon: f64,
}

#[derive(RustcEncodable)]
pub struct State {
    open: bool,
    message: String,
}

#[derive(RustcEncodable)]
pub struct Contact {
    irc: String,
    twitter: String,
    foursquare: String,
    email: String,
}

#[derive(RustcEncodable)]
pub struct Status {
    api: String,
    space: String,
    logo: String,
    url: String,
    location: Location,

    state: State,
    contact: Contact,
    issue_report_channels: [&'static str; 2],

    projects: [&'static str; 3],
}

fn main() {

    let status = Status {
        api: "0.13".to_string(),
        space: "coredump".to_string(),
        logo: "https://www.coredump.ch/logo.png".to_string(),
        url: "https://www.coredump.ch/".to_string(),
        location: Location {
            address: "Spinnereistrasse 2, 8640 Rapperswil, Switzerland".to_string(),
            lat: 47.22936,
            lon: 8.82949,
        },
        state: State {
            open: false,
            message: "Open every Monday from 20:00".to_string(),
        },
        contact: Contact {
            irc: "irc://freenode.net/#coredump".to_string(),
            twitter: "@coredump_ch".to_string(),
            foursquare: "525c20e5498e875d8231b1e5".to_string(),
            email: "danilo@coredump.ch".to_string(),
        },
        issue_report_channels: ["email", "twitter"],
        projects: [
            "https://www.coredump.ch/projekte/",
            "https://discourse.coredump.ch/c/projects",
            "https://github.com/coredump-ch/"
        ]
    };
    let encoded = json::encode(&status).unwrap();
    println!("{}", encoded);
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use hyper::header::{ContentType, Headers};
use hyper::server::{Request, Response};
use hyper_serde::serialize_pretty;
use serde_json::to_string_pretty;

use makeresponse::MakeResponse;

#[derive(Serialize)]
pub struct NativeHeadersData {
    #[serde(serialize_with="serialize_pretty")]
    headers: Headers,
}

impl MakeResponse for NativeHeadersData {
    fn content_type(&self) -> ContentType {
        ContentType::json()
    }

    fn make_response(&self, mut response: Response) {
        *response.status_mut() = self.status();
        response.headers_mut().set(self.content_type());
        let body = to_string_pretty(self).unwrap_or_else(|_| String::new());
        response.send(body.as_bytes()).unwrap();
    }
}

#[derive(Serialize)]
pub struct HeadersData {
    headers: HashMap<String, String>,
}

impl MakeResponse for HeadersData {
    fn content_type(&self) -> ContentType {
        ContentType::json()
    }

    fn make_response(&self, mut response: Response) {
        *response.status_mut() = self.status();
        response.headers_mut().set(self.content_type());
        let body = to_string_pretty(self).unwrap_or_else(|_| String::new());
        response.send(body.as_bytes()).unwrap();
    }
}

pub fn headers(request: &Request) -> HeadersData {
    // NativeHeadersData { headers: request.headers.clone() }
    let headers = request.headers
        .iter()
        .map(|h| (h.name().to_string(), h.value_string()))
        .collect::<HashMap<_, _>>();
    HeadersData { headers: headers }
}
<commit_msg>Refactor headers<commit_after>use std::collections::HashMap;

use hyper::header::{ContentType, Headers};
use hyper::server::{Request, Response};
use hyper_serde::serialize_pretty;
use serde_json::to_string_pretty;

use makeresponse::MakeResponse;

#[derive(Serialize)]
pub struct NativeHeadersData {
    #[serde(serialize_with="serialize_pretty")]
    headers: Headers,
}

impl MakeResponse for NativeHeadersData {
    fn content_type(&self) -> ContentType {
        ContentType::json()
    }

    fn make_response(&self, mut response: Response) {
        *response.status_mut() = self.status();
        response.headers_mut().set(self.content_type());
        let body = to_string_pretty(self).unwrap_or_else(|_| String::new());
        response.send(body.as_bytes()).unwrap();
    }
}

#[derive(Serialize)]
pub struct HeadersData {
    headers: HashMap<String, String>,
}

impl HeadersData {
    pub fn from(request: &Request) -> Self {
        let headers = request.headers
            .iter()
            .map(|h| (h.name().to_string(), h.value_string()))
            .collect::<HashMap<_, _>>();
        HeadersData { headers: headers }
    }
}

impl MakeResponse for HeadersData {
    fn content_type(&self) -> ContentType {
        ContentType::json()
    }

    fn make_response(&self, mut response: Response) {
        *response.status_mut() = self.status();
        response.headers_mut().set(self.content_type());
        let body = to_string_pretty(self).unwrap_or_else(|_| String::new());
        response.send(body.as_bytes()).unwrap();
    }
}

pub fn headers(request: &Request) -> HeadersData {
    // NativeHeadersData { headers: request.headers.clone() }
    HeadersData::from(request)
}
<|endoftext|>