<commit_before>#![allow(match_of_unit_variant_via_paren_dotdot)]

#![feature(plugin)]
#![plugin(plex)]

use std::io::Read;

mod lexer {
    use std::fmt::Display;
    use std::fmt::Formatter;
    use std::fmt;

    #[derive(PartialEq, Clone, Debug)]
    pub enum Token {
        Def,
        Extern,
        Delimiter, // ';' character
        OpeningParenthesis,
        ClosingParenthesis,
        Comma,
        Identifier(String),
        Number(f64),
        Operator(String),

        Whitespace,
        Comment,
    }

    // ► The Specials - Too Much Too Young

    lexer! {
        fn take_token(text: 'a) -> (Token, &'a str); // Token and the rest

        r#"[ \t\r\n]+"# => (Token::Whitespace, text),
        // "C-style" comments (/* .. */) - can't contain "*/"
        r#"/[*](~(.*[*]/.*))[*]/"# => (Token::Comment, text),
        // "C++-style" comments (// ...)
        r#"//[^\n]*"# => (Token::Comment, text),
        r#"def"# => (Token::Def, text),
        r#"extern"# => (Token::Extern, text),
        r#"[a-zA-Z_][a-zA-Z0-9_]*"# => (Token::Identifier(text.to_owned()), text),
        r#"[0-9.]+"# => {
            (if let Ok(i) = text.parse() {
                Token::Number(i)
            } else {
                panic!("float {} is out of range", text)
            }, text)
        },
        r#"\("# => (Token::OpeningParenthesis, text),
        r#"\)"# => (Token::ClosingParenthesis, text),
        r#";"# => (Token::Delimiter, text),
        r#","# => (Token::Comma, text),
        r#"[\+\-\*\/]+"# => (Token::Operator(text.to_owned()), text),
    }

    impl Token {
        pub fn contents(&self) -> Option<&str> {
            let s = match *self {
                Token::Identifier(ref s) => s,
                _ => return None,
            };
            Some(s)
        }
    }

    /// The Lexer
    #[derive(PartialEq, Clone, Debug, Copy)]
    pub struct Lexer<'a> {
        source: &'a str,
        remaining: &'a str,
    }

    impl<'a> Lexer<'a> {
        /// Create a new Lexer
        pub fn new(src: &'a str) -> Self {
            Lexer {
                source: src,
                remaining: src,
            }
        }
    }

    impl<'a> Iterator for Lexer<'a> {
        type Item = (Token, TextSpan);

        fn next(&mut self) -> Option<Self::Item> {
            loop {
                if let Some(token) = take_token(&mut self.remaining) {
                    if let (Token::Whitespace, _) = token {
                        continue;
                    } else {
                        let (token, span) = token;
                        let text_span = TextSpan::from(self.source, self.remaining, span);
                        return Some((token, text_span));
                    }
                } else {
                    return None;
                }
            }
        }
    }

    /// A structure for grouping byte offset of text spans.
    #[derive(Debug)]
    #[derive(Copy, Clone)]
    #[derive(Eq, PartialEq)]
    pub struct TextSpan {
        pub low: usize,
        pub high: usize,
    }

    impl TextSpan {
        /// Create a text span from a string and a slice of it.
        pub fn from(source: &str, remaining: &str, token: &str) -> TextSpan {
            let high = source.len() - remaining.len();
            let low = high - token.len();
            TextSpan {
                low: low,
                high: high,
            }
        }

        pub fn merge(a: TextSpan, b: TextSpan) -> TextSpan {
            let low = if a.low < b.low {
                a.low
            } else {
                b.low
            };
            let high = if a.high > b.high {
                a.high
            } else {
                b.high
            };
            TextSpan {
                low: low,
                high: high,
            }
        }
    }

    impl Display for TextSpan {
        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            write!(f, "[{}, {})", self.low, self.high)
        }
    }
}

mod ast {
    use std::collections::HashMap;

    use lexer::*;

    pub use self::ASTNode::{ExternNode, FunctionNode};
    pub use self::Expression::{LiteralExpr, VariableExpr, BinaryExpr, CallExpr};

    pub struct Program {
        pub astnodes: Vec<ASTNode>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum ASTNode {
        ExternNode(Prototype),
        FunctionNode(Function),
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Function {
        pub prototype: Prototype,
        pub body: Expression,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Prototype {
        pub name: String,
        pub args: Vec<String>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum Expression {
        LiteralExpr(f64),
        VariableExpr(String),
        BinaryExpr(String, Box<Expression>, Box<Expression>),
        CallExpr(String, Vec<Expression>),
    }
}

mod parser {
    // Grammar:
    //
    // program : [[statement | expression] Delimiter ?]*;
    // statement : [declaration | definition];
    // declaration : Extern prototype;
    // definition : Def prototype expression;
    // prototype : Identifier OpeningParenthesis [Identifier Comma ?]* ClosingParenthesis;
    // expression : [primary_expr (Operator primary_expr)*];
    // primary_expr : [Identifier | Number | call_expr | parenthesis_expr];
    // call_expr : Identifier OpeningParenthesis [expression Comma ?]* ClosingParenthesis;
    // parenthesis_expr: OpeningParenthesis expression ClosingParenthesis;
    //

    use ast::*;
    use lexer::*;

    use lexer::Token::*;

    parser! {
        fn parse_(Token, TextSpan);

        // Ignore spans
        (a, b) {
            TextSpan::merge(a, b)
        }

        program: Vec<ASTNode> {
            => vec![],
            stex[e] rest[mut p] => {
                p.push(e);
                p
            },
        }

        stex: ASTNode {
            statement[e] => e,
            expression[e] => e
        }

        rest: Vec<ASTNode> {
            program[p] => {
                p
            },
            Delimiter program[p] => {
                p
            },
        }

        statement: ASTNode {
            declaration[e] => {
                e
            },
            definition[e] => {
                e
            }
        }

        declaration: ASTNode {
            Extern prototype[p] => {
                ExternNode(p)
            },
        }

        definition: ASTNode {
            Def prototype[p] expr[e] => {
                FunctionNode(Function {
                    prototype: p,
                    body: e,
                })
            }
        }

        prototype: Prototype {
            Identifier(id) OpeningParenthesis arguments[a] ClosingParenthesis => Prototype {
                name: id,
                args: a,
            }
        }

        arguments: Vec<String> {
            => vec![],
            Identifier(id) extra_args[mut a] => {
                a.push(id);
                a
            }
        }

        extra_args: Vec<String> {
            Comma arguments[a] => {
                a
            },
            arguments[a] => {
                a
            }
        }

        expression: ASTNode {
            expr[e] => {
                let prototype = Prototype {name: "".to_string(), args: vec![]};
                FunctionNode (Function {
                    prototype: prototype,
                    body: e
                })
            }
        }

        primary_expression: Expression {
            #[no_reduce(OpeningParenthesis)]
            Identifier(id) => VariableExpr(id),
            Number(val) => LiteralExpr(val),
            Identifier(id) OpeningParenthesis many_exprs[args] ClosingParenthesis => {
                CallExpr(id, args)
            },
            parenthesis_expr[e] => e,
        }

        many_exprs: Vec<Expression> {
            => vec![],
            expr[e] extra_exprs[mut exprs] => {
                exprs.push(e);
                exprs
            }
        }
        extra_exprs: Vec<Expression> {
            Comma many_exprs[a] => {
                a
            },
            many_exprs[a] => {
                a
            }
        }

        parenthesis_expr: Expression {
            OpeningParenthesis expr[e] ClosingParenthesis => e
        }

        expr: Expression {
           primary_expression[e] binary_expr[st] => e
        }

        binary_expr: Expression {
            Operator(op) primary_expression[e] => e
        }
    }

    pub fn parse<I: Iterator<Item = (Token, TextSpan)>>
        (i: I)
         -> Result<Vec<ASTNode>, (Option<(Token, TextSpan)>, &'static str)> {
        parse_(i)
    }
}

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let lexer = lexer::Lexer::new(&s);
    for i in lexer {
        println!("{:?}", i);
    }
    println!("{:?}", parser::parse(lexer));
}

#[test]
fn test_parser() {
    use parser::*;
    use ast::*;
    use lexer::*;
    let tests = vec![
        ("extern sin();", vec![ExternNode(Prototype{name:"sin".to_string(), args:vec![]})]),
        ("", vec![]),
        ("def foo(x y x);", vec![FunctionNode(Function { prototype: Prototype { name: "foo".to_string(), args: vec!["x".to_string(), "y".to_string(), "x".to_string()] }, body: LiteralExpr(0.0) })]),
    ];

    for tc in tests {
        let parsed = lexer::Lexer::new(tc.0);
        let r = parser::parse(parsed).unwrap();
        assert_eq!(tc.1, r);
    }
}
<commit_msg>parser: add binary expr parser (without priors)<commit_after>#![allow(match_of_unit_variant_via_paren_dotdot)]
#![feature(box_syntax)]

#![feature(plugin)]
#![plugin(plex)]

use std::io::Read;

mod lexer {
    use std::fmt::Display;
    use std::fmt::Formatter;
    use std::fmt;

    #[derive(PartialEq, Clone, Debug)]
    pub enum Token {
        Def,
        Extern,
        Delimiter, // ';' character
        OpeningParenthesis,
        ClosingParenthesis,
        Comma,
        Identifier(String),
        Number(f64),
        Operator(String),

        Whitespace,
        Comment,
    }

    // ► The Specials - Too Much Too Young

    lexer! {
        fn take_token(text: 'a) -> (Token, &'a str); // Token and the rest

        r#"[ \t\r\n]+"# => (Token::Whitespace, text),
        // "C-style" comments (/* .. */) - can't contain "*/"
        r#"/[*](~(.*[*]/.*))[*]/"# => (Token::Comment, text),
        // "C++-style" comments (// ...)
        r#"//[^\n]*"# => (Token::Comment, text),
        r#"def"# => (Token::Def, text),
        r#"extern"# => (Token::Extern, text),
        r#"[a-zA-Z_][a-zA-Z0-9_]*"# => (Token::Identifier(text.to_owned()), text),
        r#"[0-9.]+"# => {
            (if let Ok(i) = text.parse() {
                Token::Number(i)
            } else {
                panic!("float {} is out of range", text)
            }, text)
        },
        r#"\("# => (Token::OpeningParenthesis, text),
        r#"\)"# => (Token::ClosingParenthesis, text),
        r#";"# => (Token::Delimiter, text),
        r#","# => (Token::Comma, text),
        r#"[\+\-\*\/]+"# => (Token::Operator(text.to_owned()), text),
    }

    impl Token {
        pub fn contents(&self) -> Option<&str> {
            let s = match *self {
                Token::Identifier(ref s) => s,
                _ => return None,
            };
            Some(s)
        }
    }

    /// The Lexer
    #[derive(PartialEq, Clone, Debug, Copy)]
    pub struct Lexer<'a> {
        source: &'a str,
        remaining: &'a str,
    }

    impl<'a> Lexer<'a> {
        /// Create a new Lexer
        pub fn new(src: &'a str) -> Self {
            Lexer {
                source: src,
                remaining: src,
            }
        }
    }

    impl<'a> Iterator for Lexer<'a> {
        type Item = (Token, TextSpan);

        fn next(&mut self) -> Option<Self::Item> {
            loop {
                if let Some(token) = take_token(&mut self.remaining) {
                    if let (Token::Whitespace, _) = token {
                        continue;
                    } else {
                        let (token, span) = token;
                        let text_span = TextSpan::from(self.source, self.remaining, span);
                        return Some((token, text_span));
                    }
                } else {
                    return None;
                }
            }
        }
    }

    /// A structure for grouping byte offset of text spans.
    #[derive(Debug)]
    #[derive(Copy, Clone)]
    #[derive(Eq, PartialEq)]
    pub struct TextSpan {
        pub low: usize,
        pub high: usize,
    }

    impl TextSpan {
        /// Create a text span from a string and a slice of it.
        pub fn from(source: &str, remaining: &str, token: &str) -> TextSpan {
            let high = source.len() - remaining.len();
            let low = high - token.len();
            TextSpan {
                low: low,
                high: high,
            }
        }

        pub fn merge(a: TextSpan, b: TextSpan) -> TextSpan {
            let low = if a.low < b.low {
                a.low
            } else {
                b.low
            };
            let high = if a.high > b.high {
                a.high
            } else {
                b.high
            };
            TextSpan {
                low: low,
                high: high,
            }
        }
    }

    impl Display for TextSpan {
        fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
            write!(f, "[{}, {})", self.low, self.high)
        }
    }
}

mod ast {
    use std::collections::HashMap;

    use lexer::*;

    pub use self::ASTNode::{ExternNode, FunctionNode};
    pub use self::Expression::{LiteralExpr, VariableExpr, BinaryExpr, CallExpr};

    pub struct Program {
        pub astnodes: Vec<ASTNode>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum ASTNode {
        ExternNode(Prototype),
        FunctionNode(Function),
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Function {
        pub prototype: Prototype,
        pub body: Expression,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub struct Prototype {
        pub name: String,
        pub args: Vec<String>,
    }

    #[derive(PartialEq, Clone, Debug)]
    pub enum Expression {
        LiteralExpr(f64),
        VariableExpr(String),
        BinaryExpr(String, Box<Expression>, Box<Expression>),
        CallExpr(String, Vec<Expression>),
    }
}

mod parser {
    // Grammar:
    //
    // program : [[statement | expression] Delimiter ?]*;
    // statement : [declaration | definition];
    // declaration : Extern prototype;
    // definition : Def prototype expression;
    // prototype : Identifier OpeningParenthesis [Identifier Comma ?]* ClosingParenthesis;
    // expression : [primary_expr (Operator primary_expr)*];
    // primary_expr : [Identifier | Number | call_expr | parenthesis_expr];
    // call_expr : Identifier OpeningParenthesis [expression Comma ?]* ClosingParenthesis;
    // parenthesis_expr: OpeningParenthesis expression ClosingParenthesis;
    //

    use ast::*;
    use lexer::*;

    use lexer::Token::*;

    parser! {
        fn parse_(Token, TextSpan);

        // Ignore spans
        (a, b) {
            TextSpan::merge(a, b)
        }

        program: Vec<ASTNode> {
            => vec![],
            stex[e] rest[mut p] => {
                p.push(e);
                p
            },
        }

        stex: ASTNode {
            statement[e] => e,
            expression[e] => e
        }

        rest: Vec<ASTNode> {
            program[p] => {
                p
            },
            Delimiter program[p] => {
                p
            },
        }

        statement: ASTNode {
            declaration[e] => {
                e
            },
            definition[e] => {
                e
            }
        }

        declaration: ASTNode {
            Extern prototype[p] => {
                ExternNode(p)
            },
        }

        definition: ASTNode {
            Def prototype[p] expr[e] => {
                FunctionNode(Function {
                    prototype: p,
                    body: e,
                })
            }
        }

        prototype: Prototype {
            Identifier(id) OpeningParenthesis arguments[a] ClosingParenthesis => Prototype {
                name: id,
                args: a,
            }
        }

        arguments: Vec<String> {
            => vec![],
            Identifier(id) extra_args[mut a] => {
                a.push(id);
                a
            }
        }

        extra_args: Vec<String> {
            Comma arguments[a] => {
                a
            },
            arguments[a] => {
                a
            }
        }

        expression: ASTNode {
            expr[e] => {
                let prototype = Prototype {name: "".to_string(), args: vec![]};
                FunctionNode (Function {
                    prototype: prototype,
                    body: e
                })
            }
        }

        primary_expression: Expression {
            #[no_reduce(OpeningParenthesis)]
            Identifier(id) => VariableExpr(id),
            Number(val) => LiteralExpr(val),
            Identifier(id) OpeningParenthesis many_exprs[args] ClosingParenthesis => {
                CallExpr(id, args)
            },
            parenthesis_expr[e] => e,
        }

        many_exprs: Vec<Expression> {
            => vec![],
            expr[e] extra_exprs[mut exprs] => {
                exprs.push(e);
                exprs
            }
        }

        extra_exprs: Vec<Expression> {
            Comma many_exprs[a] => {
                a
            },
            many_exprs[a] => {
                a
            }
        }

        parenthesis_expr: Expression {
            OpeningParenthesis expr[e] ClosingParenthesis => e
        }

        expr: Expression {
           primary_expression[e] => e,
           primary_expression[e] binary_expr[st] => {
               BinaryExpr(st.0, box e, box st.1)
           }
        }

        binary_expr: (String, Expression) {
            Operator(op) primary_expression[e] extra_binary_exprs[ebe] => {
                match ebe {
                    Some((a, b)) => (op, BinaryExpr(a, box e, box b)),
                    None => (op, e)
                }
            },
        }

        extra_binary_exprs: Option<(String, Expression)> {
            => None,
            Operator(op) expr[e] => {
                Some((op, e))
            },
        }
    }

    pub fn parse<I: Iterator<Item = (Token, TextSpan)>>
        (i: I)
         -> Result<Vec<ASTNode>, (Option<(Token, TextSpan)>, &'static str)> {
        parse_(i)
    }
}

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let lexer = lexer::Lexer::new(&s);
    for i in lexer {
        println!("{:?}", i);
    }
    println!("{:?}", parser::parse(lexer));
}

#[test]
fn test_parser() {
    use parser::*;
    use ast::*;
    use lexer::*;
    let tests = vec![
        ("extern sin();", vec![ExternNode(Prototype{name:"sin".to_string(), args:vec![]})]),
        ("", vec![]),
        ("def foo(x y x);", vec![FunctionNode(Function { prototype: Prototype { name: "foo".to_string(), args: vec!["x".to_string(), "y".to_string(), "x".to_string()] }, body: LiteralExpr(0.0) })]),
    ];

    for tc in tests {
        let parsed = lexer::Lexer::new(tc.0);
        let r = parser::parse(parsed).unwrap();
        assert_eq!(tc.1, r);
    }
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate rustc_serialize as serialize;

mod bot {
    pub mod util {
        use hyper::client::Client;
        use hyper::status::StatusCode;
        use irc::client::data::Message;
        use std::io::{Error, ErrorKind, Read};

        pub fn collect_errors(st: Result<String, String>, s: Result<String, String>) -> Result<String, String> {
            match st {
                Ok(mut st) => {
                    if let Ok(s) = s {
                        if !s.is_empty() {
                            if !st.is_empty() {
                                st.push_str("; ");
                            }

                            st.push_str(&s);
                        }

                        Ok(st)
                    } else {
                        s
                    }
                },
                Err(mut st) => {
                    if let Err(e) = s {
                        st.push_str("; ");
                        st.push_str(&e);
                    }

                    Err(st)
                }
            }
        }
        pub fn get_reply_target(msg: &Message) -> Option<&str> {
            if is_channel(&msg.args[0][..]) {
                Some(&msg.args[0][..])
            } else {
                msg.get_source_nickname()
            }
        }
        pub fn http_get(url: &str) -> Result<String, Error> {
            let mut resp = match Client::new().get(url).send() {
                Ok(resp) => resp,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp.status {
                StatusCode::Ok => {
                    let mut body = String::new();
                    try!(resp.read_to_string(&mut body));
                    Ok(body)
                },
                StatusCode::BadRequest => Err(Error::new(ErrorKind::Other, "HTTP Bad Request")),
                StatusCode::NotFound => Err(Error::new(ErrorKind::Other, "HTTP Not Found")),
                _ => Err(Error::new(ErrorKind::Other, format!("{:?}", resp.status)))
            }
        }
        pub fn is_channel(s: &str) -> bool {
            s.chars().take(1).next().map(|c| c == '#' || c == '&').unwrap_or(false) &&
                s.chars().skip(1).filter(|c| *c == ' ' || *c == '\u{0007}' || *c == ',').next().is_none()
        }
    }
    pub mod regexmatch {
        use irc::client::data::{Command, Message};
        use regex::Regex;
        use serialize::json;
        use std::collections::HashMap;
        use std::collections::hash_map::Entry;
        use std::io::{Error, ErrorKind};
        use super::Subscriber;
        use super::util::{collect_errors, get_reply_target, http_get};

        pub struct RegexMatch {
            gapi_key: String,
            yt_cache: HashMap<String, String>
        }

        fn weather_k_to_c(k: f64) -> f64 {
            k - 273.15
        }
        fn weather_k_to_f(k: f64) -> f64 {
            let c = weather_k_to_c(k);
            1.8 * c + 32.0
        }
        fn weather_handler(_: &mut RegexMatch, loc: &str) -> Result<String, Error> {
            let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}", loc);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::Json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp_json.find("message") {
                Some(e) => {
                    let e = e.as_string().expect("OpenWeatherMap API error - 'message' was not a string");

                    return Err(Error::new(ErrorKind::Other, e))
                },
                None => {}
            };

            let name = resp_json
                .find("name").expect("OpenWeatherMap API error - could not find 'name'")
                .as_string().expect("OpenWeatherMap API error - 'name' was not a string");
            let description = resp_json
                .find("weather").expect("OpenWeatherMap API error - could not find 'weather'")
                .as_array().expect("OpenWeatherMap API error - 'weather' was not an array")[0]
                .find("description").expect("OpenWeatherMap API error - could not find 'description'")
                .as_string().expect("OpenWeatherMap API error - 'description' was not a string");
            let main = resp_json
                .find("main").expect("OpenWeatherMap API error - could not find 'main'");
            let temp_k = main
                .find("temp").expect("OpenWeatherMap API error - could not find 'temp'")
                .as_f64().expect("OpenWeatherMap API error - 'temp' was not a number");
            let humidity = main
                .find("humidity").expect("OpenWeatherMap API error - could not find 'humidity'")
                .as_f64().expect("OpenWeatherMap API error - 'humidity' was not a number");
            let wind_speed = resp_json
                .find("wind").expect("OpenWeatherMap API error - could not find 'wind'")
                .find("speed").expect("OpenWeatherMap API error - could not find 'speed'")
                .as_f64().expect("OpenWeatherMap API error - 'speed' was not a number");

            Ok(format!("{} weather: {:.2} \u{00B0}F ({:.2} \u{00B0}C), {}% humidity, {} and wind {:.2} m/s",
                       name, weather_k_to_f(temp_k), weather_k_to_c(temp_k), humidity, description, wind_speed))
        }
        fn yt_parse_time(s: &str) -> String {
            let mut in_time = false;
            let mut start_idx: Option<usize> = None;
            let mut prev_char: char = '\u{0000}';
            let mut length = String::new();

            for (idx, c) in s.chars().enumerate() {
                match c {
                    '0' ... '9' => {prev_char = c; continue}
                    'P' => start_idx = Some(idx + 1),
                    'T' => {
                        start_idx = Some(idx + 1);
                        in_time = true
                    },
                    _ => {
                        if length.len() > 0 {
                            length.push_str(", ");
                        }

                        length.push_str(&s[start_idx.unwrap() .. idx]);
                        length.push(' ');
                        length.push_str(match c {
                            'Y' => "year",
                            'M' => if in_time {"minute"} else {"month"},
                            'D' => "day",
                            'H' => "hour",
                            'S' => "second",
                            _ => panic!("Unexpected character in ISO 8601 duration string")
                        });

                        if idx - start_idx.unwrap() > 1 || prev_char != '1' {
                            length.push('s');
                        }

                        start_idx = Some(idx + 1);
                    }
                }
            }

            length
        }
        fn yt_handler(this: &mut RegexMatch, id: &str) -> Result<String, Error> {
            Ok(match this.yt_cache.entry(id.to_owned()) {
                Entry::Occupied(entry) => entry.get().clone(),
                Entry::Vacant(entry) => {
                    let url = format!("https://www.googleapis.com/youtube/v3/videos?id={}&key={}&part=snippet,contentDetails&fields=items(snippet/title,snippet/channelTitle,contentDetails/duration)", id, this.gapi_key);

                    let resp = &try!(http_get(&url))[..];
                    let resp_json = match json::Json::from_str(resp) {
                        Ok(resp_json) => resp_json,
                        Err(e) => return Err(Error::new(ErrorKind::Other, e))
                    };

                    // I chose to panic here on any errors, since if we get a response from Youtube
                    // it should be of the correct form - their API shouldn't change underneath us.
                    let ref items = resp_json
                        .find("items").expect("Youtube API error - could not find 'items'")
                        .as_array().expect("Youtube API error - 'items' was not an array")[0];
                    let snippet = items
                        .find("snippet").expect("Youtube API error - could not find 'snippet'");
                    let title = snippet
                        .find("title").expect("Youtube API error - could not find 'title'")
                        .as_string().expect("Youtube API error - 'title' was not a string");
                    let channel_title = snippet
                        .find("channelTitle").expect("Youtube API error - could not find 'channelTitle'")
                        .as_string().expect("Youtube API error - 'channelTitle' was not a string");
                    let duration = items
                        .find("contentDetails").expect("Youtube API error - could not find 'contentDetails'")
                        .find("duration").expect("Youtube API error - could not find 'duration'")
                        .as_string().expect("Youtube API error - 'duration' was not a string");

                    let res = format!("\"{}\" by {} ({})", title, channel_title, yt_parse_time(duration));
                    entry.insert(res).clone()
                }
            })
        }
        fn xkcd_handler(_: &mut RegexMatch, id: &str) -> Result<String, Error> {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::Json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            let title = resp_json
                .find("title").expect("XKCD API error - could not find 'title'")
                .as_string().expect("XKCD API error - 'title' was not a string");
            let year = resp_json
                .find("year").expect("XKCD API error - could not find 'year'")
                .as_string().expect("XKCD API error - 'year' was not a string");
            let month = resp_json
                .find("month").expect("XKCD API error - could not find 'month'")
                .as_string().expect("XKCD API error - 'month' was not a string");
            let day = resp_json
                .find("day").expect("XKCD API error - could not find 'day'")
                .as_string().expect("XKCD API error - 'day' was not a string");

            Ok(format!("{} ({}-{}-{})", title, year, month, day))
        }

        lazy_static! {
            static ref URL_RES: Vec<(Regex, fn(&mut RegexMatch, &str) -> Result<String, Error>)> =
                vec![(Regex::new(r"^!weather\s*(.*)\s*$").unwrap(), weather_handler),
                     (Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(), yt_handler),
                     (Regex::new(r"xkcd\.com/(\d+)").unwrap(), xkcd_handler)];
        }

        impl RegexMatch {
            pub fn new(gapi_key: &str) -> RegexMatch {
                RegexMatch {
                    gapi_key: gapi_key.to_owned(),
                    yt_cache: HashMap::new()
                }
            }
        }

        impl Subscriber for RegexMatch {
            fn on_message<'a>(&mut self, msg: &'a Message) -> Result<Option<Command>, Error> {
                if msg.command == "PRIVMSG" {
                    if let Some(reply_target) = get_reply_target(msg) {
                        if let Some(ref suffix) = msg.suffix {
                            match URL_RES.iter().map(|&(ref re, handler)| {
                                re
                                    .captures_iter(suffix)
                                    .filter_map(|capture| capture.at(1))
                                    .map(|capture| match handler(self, capture) {
                                        Ok(res) => Ok(res),
                                        Err(e) => Err(format!("{:?}", e))
                                    })
                                    .fold(Ok(String::new()), collect_errors)
                            }).fold(Ok(String::new()), collect_errors) {
                                Ok(res) => Ok(Some(Command::PRIVMSG(reply_target.to_owned(), res))),
                                Err(e) => Err(Error::new(ErrorKind::Other, e))
                            }
                        } else {
                            Err(Error::new(ErrorKind::Other, "No message text."))
                        }
                    } else {
                        Err(Error::new(ErrorKind::Other, "Unable to determine reply target."))
                    }
                } else {
                    Ok(None)
                }
            }
        }
    }

    use irc::client::data::{Command, Config, Message};
    use irc::client::server::{IrcServer, NetIrcServer, Server};
    use irc::client::server::utils::ServerExt;
    use std::io::Error;

    pub struct Bot<'a> {
        server: NetIrcServer,
        subscribers: Vec<&'a mut (Subscriber + 'a)>
    }
    pub trait Subscriber {
        fn on_message<'a>(&mut self, &'a Message) -> Result<Option<Command>, Error>;
    }

    impl<'a> Bot<'a> {
        pub fn new(config: Config) -> Result<Bot<'a>, Error> {
            let server = try!(IrcServer::from_config(config));
            try!(server.identify());

            Ok(Bot {
                server: server,
                subscribers: vec![]
            })
        }
        pub fn add_subscriber(&mut self, sub: &'a mut Subscriber) {
            self.subscribers.push(sub);
        }
        pub fn loop_forever(&mut self) -> Error {
            loop {
                if let Err(e) = self.loop_once() {
                    return e;
                }
            }
        }
        pub fn loop_once(&mut self) -> Result<(), Error> {
            let &mut Bot {ref mut server, ref mut subscribers} = self;

            match server.iter().next() {
                Some(Ok(msg)) => subscribers.iter_mut().map(|sub| sub.on_message(&msg)).map(|msg| match msg {
                    Ok(Some(msg)) => server.send(msg),
                    Ok(None) => Ok(()),
                    Err(e) => {
                        let targets = server.config().channels().iter().fold(String::new(), |mut st, chan| {
                            if st.len() > 0 {
                                st.push(',');
                            }
                            st.push_str(chan);
                            st
                        });

                        //TODO: Log here
                        println!("{:?} {:?}", targets, e);

                        if targets.len() > 0 {
                            server.send(Command::PRIVMSG(targets, format!("{}", e)))
                        } else {
                            Ok(())
                        }
                    }
                }).find(|res| res.is_err()).map_or(Ok(()), |r| r),
                Some(Err(e)) => Err(e),
                None => Ok(())
            }
        }
    }
}

use bot::Bot;
use bot::regexmatch::RegexMatch;
use irc::client::data::Config;
use std::fs::File;
use std::io::{Error, Read};
use std::path::Path;

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}

fn main() {
    let mut regex_match = RegexMatch::new(&String::from_utf8(file_get(Path::new("gapi_key.dat"))
                                                             .ok().expect("gapi_key.dat should be on the path"))
                                          .ok().expect("gapi_key.dat should contain only the Google API key (no trailing newlines)"));

    let mut bot = Bot::new(Config::load(Path::new("config.json")).unwrap()).unwrap();

    bot.add_subscriber(&mut regex_match);
    bot.loop_forever();
}
<commit_msg>Document collect_errors, rename to collate_results<commit_after>extern crate hyper;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate rustc_serialize as serialize;

mod bot {
    pub mod util {
        use hyper::client::Client;
        use hyper::status::StatusCode;
        use irc::client::data::Message;
        use std::io::{Error, ErrorKind, Read};

        // A function to collect a series of successes or errors into a single string.
        // Errors take priority over everything else and are *not* displayed alongside successes.
        // Otherwise, all successes will be together, or all errors will be together.
        pub fn collate_results(st: Result<String, String>, s: Result<String, String>) -> Result<String, String> {
            match st {
                Ok(mut st) => {
                    if let Ok(s) = s {
                        if !s.is_empty() {
                            if !st.is_empty() {
                                st.push_str("; ");
                            }

                            st.push_str(&s);
                        }

                        Ok(st)
                    } else {
                        s
                    }
                },
                Err(mut st) => {
                    if let Err(e) = s {
                        st.push_str("; ");
                        st.push_str(&e);
                    }

                    Err(st)
                }
            }
        }
        pub fn get_reply_target(msg: &Message) -> Option<&str> {
            if is_channel(&msg.args[0][..]) {
                Some(&msg.args[0][..])
            } else {
                msg.get_source_nickname()
            }
        }
        pub fn http_get(url: &str) -> Result<String, Error> {
            let mut resp = match Client::new().get(url).send() {
                Ok(resp) => resp,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp.status {
                StatusCode::Ok => {
                    let mut body = String::new();
                    try!(resp.read_to_string(&mut body));
                    Ok(body)
                },
                StatusCode::BadRequest => Err(Error::new(ErrorKind::Other, "HTTP Bad Request")),
                StatusCode::NotFound => Err(Error::new(ErrorKind::Other, "HTTP Not Found")),
                _ => Err(Error::new(ErrorKind::Other, format!("{:?}", resp.status)))
            }
        }
        pub fn is_channel(s: &str) -> bool {
            s.chars().take(1).next().map(|c| c == '#' || c == '&').unwrap_or(false) &&
                s.chars().skip(1).filter(|c| *c == ' ' || *c == '\u{0007}' || *c == ',').next().is_none()
        }
    }
    pub mod regexmatch {
        use irc::client::data::{Command, Message};
        use regex::Regex;
        use serialize::json;
        use std::collections::HashMap;
        use std::collections::hash_map::Entry;
        use std::io::{Error, ErrorKind};
        use super::Subscriber;
        use super::util::{collate_results, get_reply_target, http_get};

        pub struct RegexMatch {
            gapi_key: String,
            yt_cache: HashMap<String, String>
        }

        fn weather_k_to_c(k: f64) -> f64 {
            k - 273.15
        }
        fn weather_k_to_f(k: f64) -> f64 {
            let c = weather_k_to_c(k);
            1.8 * c + 32.0
        }
        fn weather_handler(_: &mut RegexMatch, loc: &str) -> Result<String, Error> {
            let url = format!("http://api.openweathermap.org/data/2.5/weather?q={}", loc);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::Json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            match resp_json.find("message") {
                Some(e) => {
                    let e = e.as_string().expect("OpenWeatherMap API error - 'message' was not a string");

                    return Err(Error::new(ErrorKind::Other, e))
                },
                None => {}
            };

            let name = resp_json
                .find("name").expect("OpenWeatherMap API error - could not find 'name'")
                .as_string().expect("OpenWeatherMap API error - 'name' was not a string");
            let description = resp_json
                .find("weather").expect("OpenWeatherMap API error - could not find 'weather'")
                .as_array().expect("OpenWeatherMap API error - 'weather' was not an array")[0]
                .find("description").expect("OpenWeatherMap API error - could not find 'description'")
                .as_string().expect("OpenWeatherMap API error - 'description' was not a string");
            let main = resp_json
                .find("main").expect("OpenWeatherMap API error - could not find 'main'");
            let temp_k = main
                .find("temp").expect("OpenWeatherMap API error - could not find 'temp'")
                .as_f64().expect("OpenWeatherMap API error - 'temp' was not a number");
            let humidity = main
                .find("humidity").expect("OpenWeatherMap API error - could not find 'humidity'")
                .as_f64().expect("OpenWeatherMap API error - 'humidity' was not a number");
            let wind_speed = resp_json
                .find("wind").expect("OpenWeatherMap API error - could not find 'wind'")
                .find("speed").expect("OpenWeatherMap API error - could not find 'speed'")
                .as_f64().expect("OpenWeatherMap API error - 'speed' was not a number");

            Ok(format!("{} weather: {:.2} \u{00B0}F ({:.2} \u{00B0}C), {}% humidity, {} and wind {:.2} m/s",
                       name, weather_k_to_f(temp_k), weather_k_to_c(temp_k), humidity, description, wind_speed))
        }
        fn yt_parse_time(s: &str) -> String {
            let mut in_time = false;
            let mut start_idx: Option<usize> = None;
            let mut prev_char: char = '\u{0000}';
            let mut length = String::new();

            for (idx, c) in s.chars().enumerate() {
                match c {
                    '0' ... '9' => {prev_char = c; continue}
                    'P' => start_idx = Some(idx + 1),
                    'T' => {
                        start_idx = Some(idx + 1);
                        in_time = true
                    },
                    _ => {
                        if length.len() > 0 {
                            length.push_str(", ");
                        }

                        length.push_str(&s[start_idx.unwrap() .. idx]);
                        length.push(' ');
                        length.push_str(match c {
                            'Y' => "year",
                            'M' => if in_time {"minute"} else {"month"},
                            'D' => "day",
                            'H' => "hour",
                            'S' => "second",
                            _ => panic!("Unexpected character in ISO 8601 duration string")
                        });

                        if idx - start_idx.unwrap() > 1 || prev_char != '1' {
                            length.push('s');
                        }

                        start_idx = Some(idx + 1);
                    }
                }
            }

            length
        }
        fn yt_handler(this: &mut RegexMatch, id: &str) -> Result<String, Error> {
            Ok(match this.yt_cache.entry(id.to_owned()) {
                Entry::Occupied(entry) => entry.get().clone(),
                Entry::Vacant(entry) => {
                    let url = format!("https://www.googleapis.com/youtube/v3/videos?id={}&key={}&part=snippet,contentDetails&fields=items(snippet/title,snippet/channelTitle,contentDetails/duration)", id, this.gapi_key);

                    let resp = &try!(http_get(&url))[..];
                    let resp_json = match json::Json::from_str(resp) {
                        Ok(resp_json) => resp_json,
                        Err(e) => return Err(Error::new(ErrorKind::Other, e))
                    };

                    // I chose to panic here on any errors, since if we get a response from Youtube
                    // it should be of the correct form - their API shouldn't change underneath us.
                    let ref items = resp_json
                        .find("items").expect("Youtube API error - could not find 'items'")
                        .as_array().expect("Youtube API error - 'items' was not an array")[0];
                    let snippet = items
                        .find("snippet").expect("Youtube API error - could not find 'snippet'");
                    let title = snippet
                        .find("title").expect("Youtube API error - could not find 'title'")
                        .as_string().expect("Youtube API error - 'title' was not a string");
                    let channel_title = snippet
                        .find("channelTitle").expect("Youtube API error - could not find 'channelTitle'")
                        .as_string().expect("Youtube API error - 'channelTitle' was not a string");
                    let duration = items
                        .find("contentDetails").expect("Youtube API error - could not find 'contentDetails'")
                        .find("duration").expect("Youtube API error - could not find 'duration'")
                        .as_string().expect("Youtube API error - 'duration' was not a string");

                    let res = format!("\"{}\" by {} ({})", title, channel_title, yt_parse_time(duration));
                    entry.insert(res).clone()
                }
            })
        }
        fn xkcd_handler(_: &mut RegexMatch, id: &str) -> Result<String, Error> {
            let url = format!("https://xkcd.com/{}/info.0.json", id);

            let resp = &try!(http_get(&url))[..];
            let resp_json = match json::Json::from_str(resp) {
                Ok(resp_json) => resp_json,
                Err(e) => return Err(Error::new(ErrorKind::Other, e))
            };

            let title = resp_json
                .find("title").expect("XKCD API error - could not find 'title'")
                .as_string().expect("XKCD API error - 'title' was not a string");
            let year = resp_json
                .find("year").expect("XKCD API error - could not find 'year'")
                .as_string().expect("XKCD API error - 'year' was not a string");
            let month = resp_json
                .find("month").expect("XKCD API error - could not find 'month'")
                .as_string().expect("XKCD API error - 'month' was not a string");
            let day = resp_json
                .find("day").expect("XKCD API error - could not find 'day'")
                .as_string().expect("XKCD API error - 'day' was not a string");

            Ok(format!("{} ({}-{}-{})", title, year, month, day))
        }

        lazy_static! {
            static ref URL_RES: Vec<(Regex, fn(&mut RegexMatch, &str) -> Result<String, Error>)> =
                vec![(Regex::new(r"^!weather\s*(.*)\s*$").unwrap(), weather_handler),
                     (Regex::new(r"(?:(?:youtube\.com/watch\?\S*?v=)|(?:youtu\.be/))([\w-]+)").unwrap(), yt_handler),
                     (Regex::new(r"xkcd\.com/(\d+)").unwrap(), xkcd_handler)];
        }

        impl RegexMatch {
            pub fn new(gapi_key: &str) -> RegexMatch {
                RegexMatch {
                    gapi_key: gapi_key.to_owned(),
                    yt_cache: HashMap::new()
                }
            }
        }

        impl Subscriber for RegexMatch {
            fn on_message<'a>(&mut self, msg: &'a Message) -> Result<Option<Command>, Error> {
                if msg.command == "PRIVMSG" {
                    if let Some(reply_target) = get_reply_target(msg) {
                        if let Some(ref suffix) = msg.suffix {
                            match URL_RES.iter().map(|&(ref re, handler)| {
                                re
                                    .captures_iter(suffix)
                                    .filter_map(|capture| capture.at(1))
                                    .map(|capture| match handler(self, capture) {
                                        Ok(res) => Ok(res),
                                        Err(e) => Err(format!("{:?}", e))
                                    })
                                    .fold(Ok(String::new()), collate_results)
                            }).fold(Ok(String::new()), collate_results) {
                                Ok(res) => Ok(Some(Command::PRIVMSG(reply_target.to_owned(), res))),
                                Err(e) => Err(Error::new(ErrorKind::Other, e))
                            }
                        } else {
                            Err(Error::new(ErrorKind::Other, "No message text."))
                        }
                    } else {
                        Err(Error::new(ErrorKind::Other, "Unable to determine reply target."))
                    }
                } else {
                    Ok(None)
                }
            }
        }
    }

    use irc::client::data::{Command, Config, Message};
    use irc::client::server::{IrcServer, NetIrcServer, Server};
    use irc::client::server::utils::ServerExt;
    use std::io::Error;

    pub struct Bot<'a> {
        server: NetIrcServer,
        subscribers: Vec<&'a mut (Subscriber + 'a)>
    }
    pub trait Subscriber {
        fn on_message<'a>(&mut self, &'a Message) -> Result<Option<Command>, Error>;
    }

    impl<'a> Bot<'a> {
        pub fn new(config: Config) -> Result<Bot<'a>, Error> {
            let server = try!(IrcServer::from_config(config));
            try!(server.identify());

            Ok(Bot {
                server: server,
                subscribers: vec![]
            })
        }
        pub fn add_subscriber(&mut self, sub: &'a mut Subscriber) {
            self.subscribers.push(sub);
        }
        pub fn loop_forever(&mut self) -> Error {
            loop {
                if let Err(e) = self.loop_once() {
                    return e;
                }
            }
        }
        pub fn loop_once(&mut self) -> Result<(), Error> {
            let &mut Bot {ref mut server, ref mut subscribers} = self;

            match server.iter().next() {
                Some(Ok(msg)) => subscribers.iter_mut().map(|sub| sub.on_message(&msg)).map(|msg| match msg {
                    Ok(Some(msg)) => server.send(msg),
                    Ok(None) => Ok(()),
                    Err(e) => {
                        let targets = server.config().channels().iter().fold(String::new(), |mut st, chan| {
                            if st.len() > 0 {
                                st.push(',');
                            }
                            st.push_str(chan);
                            st
                        });

                        //TODO: Log here
                        println!("{:?} {:?}", targets, e);

                        if targets.len() > 0 {
                            server.send(Command::PRIVMSG(targets, format!("{}", e)))
                        } else {
                            Ok(())
                        }
                    }
                }).find(|res| res.is_err()).map_or(Ok(()), |r| r),
                Some(Err(e)) => Err(e),
                None => Ok(())
            }
        }
    }
}

use bot::Bot;
use bot::regexmatch::RegexMatch;
use irc::client::data::Config;
use std::fs::File;
use std::io::{Error, Read};
use std::path::Path;

fn file_get(p: &Path) -> Result<Vec<u8>, Error> {
    Ok(try!(File::open(p)).bytes().map(|b| b.unwrap()).collect::<Vec<u8>>())
}

fn main() {
    let mut regex_match = RegexMatch::new(&String::from_utf8(file_get(Path::new("gapi_key.dat"))
                                                             .ok().expect("gapi_key.dat should be on the path"))
                                          .ok().expect("gapi_key.dat should contain only the Google API key (no trailing newlines)"));

    let mut bot = Bot::new(Config::load(Path::new("config.json")).unwrap()).unwrap();

    bot.add_subscriber(&mut regex_match);
    bot.loop_forever();
}
<|endoftext|>


<commit_before>extern crate crypto;
extern crate base64;
extern crate byteorder;
extern crate docopt;
extern crate rustc_serialize;
extern crate rpassword;
extern crate ring;
extern crate untrusted;
#[macro_use]
extern crate error_chain;

use std::process;
use std::mem;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::{OpenOptions, File};
use std::path::Path;

use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair;
use ring::digest;
use crypto::bcrypt_pbkdf::bcrypt_pbkdf;

use docopt::Docopt;

mod structs;
mod errors;

use structs::*;
use errors::*;

const USAGE: &'static str = "
signify-rs

Usage:
  signify -h
  signify -G [-n] [-c <comment>] -p <pubkey> -s <seckey>
  signify -S [-x <sigfile>] -s <seckey> -m <message>
  signify -V [-x <sigfile>] -p <pubkey> -m <message>

Options:
  -h --help     Show this screen.
  -c <comment>  Specify the comment to be added during key generation.
  -m <message>  When signing, the file containing the message to sign.  When verifying, the file containing the
                message to verify.  When verifying with -e, the file to create.
  -n            Do not ask for a passphrase during key generation. Otherwise, signify will prompt the user for a
                passphrase to protect the secret key.
  -p <pubkey>   Public key produced by -G, and used by -V to check a signature.
  -s <seckey>   Secret (private) key produced by -G, and used by -S to sign a message.
  -x <sigfile>  The signature file to create or verify.  The default is <message>.sig.
";

#[allow(non_snake_case)]
#[derive(Debug, RustcDecodable)]
struct Args {
    flag_G: bool,
    flag_S: bool,
    flag_V: bool,

    flag_x: Option<String>,
    flag_c: Option<String>,

    flag_p: String,
    flag_s: String,
    flag_m: String,
    flag_n: bool,
}

enum FileContent {
    PublicKey(PublicKey),
    PrivateKey(PrivateKey),
    Signature(Signature),
}

fn write_base64_file<P: AsRef<Path>>(file: P, comment: &str, buf: &[u8]) -> Result<()> {
    let mut f = try!(OpenOptions::new().write(true).create_new(true).open(file));

    try!(write!(f, "{}", COMMENTHDR));
    try!(write!(f, "{}\n", comment));
    let out = base64::encode(buf);
    try!(write!(f, "{}\n", out));

    Ok(())
}

fn read_base64_file<P: AsRef<Path>>(file: P) -> Result<FileContent> {
    let file_display = format!("{}", file.as_ref().display());
    let f = try!(File::open(file));
    let mut reader = BufReader::new(f);

    let mut comment_line = String::new();
    let len = try!(reader.read_line(&mut comment_line));

    if len == 0 || len < COMMENTHDRLEN || !comment_line.starts_with(COMMENTHDR) {
        return Err(format!("invalid comment in {}; must start with '{}'", file_display, COMMENTHDR).into());
    }

    if &comment_line[len-1..len] != "\n" {
        return Err(format!("missing new line after comment in {}", file_display).into());
    }

    if len > COMMENTHDRLEN + COMMENTMAXLEN {
        return Err("comment too long".into());
    }

    let mut base64_line = String::new();
    let len = try!(reader.read_line(&mut base64_line));

    if len == 0 {
        return Err(format!("missing line in {}", file_display).into());
    }

    if &base64_line[len-1..len] != "\n" {
        return Err(format!("missing new line after comment in {}", file_display).into());
    }

    let base64_line = &base64_line[0..len-1];

    let data = try!(base64::decode(base64_line)
                    .chain_err(|| format!("invalid base64 encoding in {}", file_display)));

    if &data[0..2] != PKGALG {
        return Err(format!("unsupported file {}", file_display).into());
    }

    match data.len() {
        x if x == mem::size_of::<PublicKey>() => {
            PublicKey::from_buf(&data)
                .map(FileContent::PublicKey)
        }
        x if x == mem::size_of::<PrivateKey>() => {
            PrivateKey::from_buf(&data)
                .map(FileContent::PrivateKey)
        }
        x if x == mem::size_of::<Signature>() => {
            Signature::from_buf(&data)
                .map(FileContent::Signature)
        },
        _ => {
            Err(format!("unsupported file {}", file_display).into())
        }
    }
}

fn verify(pubkey_path: String, msg_path: String, signature_path: Option<String>) -> Result<()> {
    let pkey = match read_base64_file(&pubkey_path) {
        Ok(FileContent::PublicKey(pkey)) => pkey,
        _ => return Err("an error occured.".into()),
    };

    let signature_path = match signature_path {
        Some(path) => path,
        None => format!("{}.sig", msg_path)
    };

    let signature = match read_base64_file(&signature_path) {
        Ok(FileContent::Signature(sig)) => sig,
        _ => return Err(format!("Can't read signature from {}", signature_path).into()),
    };

    let mut msgfile = try!(File::open(&msg_path).chain_err(|| read_error(&msg_path)));
    let mut msg = vec![];
    try!(msgfile.read_to_end(&mut msg).chain_err(|| read_error(&msg_path)));

    if signature.keynum != pkey.keynum {
        return Err("signature verification failed: checked against wrong key".into());
    }

    if signature.verify(&msg, &pkey) {
        println!("Signature Verified");
        Ok(())
    } else {
        Err("signature verification failed".into())
    }
}

fn sign(seckey_path: String, msg_path: String, signature_path: Option<String>) -> Result<()> {
    let mut skey = match read_base64_file(&seckey_path) {
        Ok(FileContent::PrivateKey(skey)) => skey,
        _ => return Err("an error occured.".into()),
    };

    let rounds = skey.kdfrounds;
    let xorkey = try!(kdf(&skey.salt, rounds, false, SECRETBYTES));

    for (prv, xor) in skey.seckey.iter_mut().zip(xorkey.iter()) {
        *prv = *prv ^ xor;
    }
    let skey = skey;

    let mut msgfile = try!(File::open(&msg_path)
                           .chain_err(|| read_error(&msg_path)).into());
    let mut msg = vec![];
    try!(msgfile.read_to_end(&mut msg).chain_err(|| read_error(&msg_path)));

    let signature_path = match signature_path {
        Some(path) => path,
        None => format!("{}.sig", msg_path)
    };

    let sig = try!(skey.sign(&msg).chain_err(|| "Failed to sign message"));

    let mut out = vec![];
    try!(sig.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let sig_comment = "signature from signify secret key";

    write_base64_file(&signature_path, sig_comment, &out)
        .chain_err(|| "Failed to write signature file")
}

fn read_password(prompt: &str) -> Result<String> {
    let mut stdout = std::io::stdout();
    try!(stdout.write_all(prompt.as_bytes()).chain_err(|| "Write to stdout failed"));
    try!(stdout.flush().chain_err(|| "Flushing stdout failed"));

    rpassword::read_password()
        .chain_err(|| "unable to read passphrase")
}

fn kdf(salt: &[u8], rounds: u32, confirm: bool, keylen: usize) -> Result<Vec<u8>> {
    let mut result = vec![0; keylen];
    if rounds == 0 {
        return Ok(result);
    }

    let passphrase = try!(read_password("passphrase: "));

    if confirm {
        let confirm_passphrase = try!(read_password("confirm passphrase: "));

        if passphrase != confirm_passphrase {
            return Err("passwords don't match".into());
        }
    }

    bcrypt_pbkdf(passphrase.as_bytes(), salt, rounds, &mut result);
    Ok(result)
}

fn generate(pubkey_path: String, privkey_path: String, comment: Option<String>, kdfrounds: u32) -> Result<()> {
    let comment = match comment {
        Some(s) => s,
        None    => "signify".into()
    };

    let mut keynum = [0; KEYNUMLEN];
    try!(SystemRandom.fill(&mut keynum).chain_err(|| "Can't fill keynum randomly"));

    let (_, keypair_bytes) = try!(Ed25519KeyPair::generate_serializable(&SystemRandom));
    let mut skey = keypair_bytes.private_key;
    let pkey = keypair_bytes.public_key;

    let mut salt = [0; 16];
    try!(SystemRandom.fill(&mut salt).chain_err(|| "Can't fill salt randomly"));

    let xorkey = try!(kdf(&salt, kdfrounds, true, SECRETBYTES));

    for (prv, xor) in skey.iter_mut().zip(xorkey.iter()) {
        *prv = *prv ^ xor;
    }

    // signify stores the extended key as the private key,
    // that is the 32 byte of the secret key, followed by the 32 byte of the public key,
    // summing up to 64 byte.
    //
    //  *ring* separates them, so we need to stick them together again.
    let mut complete_key = [0; 64];
    complete_key[0..32].copy_from_slice(&skey[0..32]);
    complete_key[32..].copy_from_slice(&pkey);

    // Store private key
    let digest = digest::digest(&digest::SHA512, &complete_key);
    let mut checksum = [0; 8];
    checksum.copy_from_slice(&digest.as_ref()[0..8]);

    let private_key = PrivateKey {
        pkgalg: PKGALG,
        kdfalg: KDFALG,
        kdfrounds: kdfrounds,
        salt: salt,
        checksum: checksum,
        keynum: keynum,
        seckey: complete_key,
    };

    let mut out = vec![];
    try!(private_key.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let priv_comment = format!("{} secret key", comment);
    try!(write_base64_file(&privkey_path, &priv_comment, &out)
        .chain_err(|| write_error(&privkey_path))
    );

    // Store public key
    let public_key = PublicKey::with_key_and_keynum(pkey, keynum);

    let mut out = vec![];
    try!(public_key.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let pub_comment = format!("{} public key", comment);
    write_base64_file(&pubkey_path, &pub_comment, &out)
        .chain_err(|| write_error(&pubkey_path))
}

fn human(res: Result<()>) {
    match res {
        Err(e) => {
            let mut it = e.iter();
            let e = it.next().unwrap(); // We have an error, so we can definitely unwrap it.
            let next_err = it.next();
            if let Some(next_err) = next_err {
                println!("{}: {}", e, next_err);
            } else {
                println!("{}", e);
            }

            process::exit(1);
        },
        Ok(()) => {}
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_V {
        human(verify(args.flag_p, args.flag_m, args.flag_x));
    } else if args.flag_G {
        let rounds = if args.flag_n { 0 } else { 42 };
        human(generate(args.flag_p, args.flag_s, args.flag_c, rounds));
    } else if args.flag_S {
        human(sign(args.flag_s, args.flag_m, args.flag_x));
    }
}
<commit_msg>Auto merge of #13 - briansmith:robust-parsing, r=badboy<commit_after>extern crate crypto;
extern crate base64;
extern crate byteorder;
extern crate docopt;
extern crate rustc_serialize;
extern crate rpassword;
extern crate ring;
extern crate untrusted;
#[macro_use]
extern crate error_chain;

use std::process;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::{OpenOptions, File};
use std::path::Path;

use ring::rand::SystemRandom;
use ring::signature::Ed25519KeyPair;
use ring::digest;
use crypto::bcrypt_pbkdf::bcrypt_pbkdf;

use docopt::Docopt;

mod structs;
mod errors;

use structs::*;
use errors::*;

const USAGE: &'static str = "
signify-rs

Usage:
  signify -h
  signify -G [-n] [-c <comment>] -p <pubkey> -s <seckey>
  signify -S [-x <sigfile>] -s <seckey> -m <message>
  signify -V [-x <sigfile>] -p <pubkey> -m <message>

Options:
  -h --help     Show this screen.
  -c <comment>  Specify the comment to be added during key generation.
  -m <message>  When signing, the file containing the message to sign.  When verifying, the file containing the
                message to verify.  When verifying with -e, the file to create.
  -n            Do not ask for a passphrase during key generation. Otherwise, signify will prompt the user for a
                passphrase to protect the secret key.
  -p <pubkey>   Public key produced by -G, and used by -V to check a signature.
  -s <seckey>   Secret (private) key produced by -G, and used by -S to sign a message.
  -x <sigfile>  The signature file to create or verify.  The default is <message>.sig.
";

#[allow(non_snake_case)]
#[derive(Debug, RustcDecodable)]
struct Args {
    flag_G: bool,
    flag_S: bool,
    flag_V: bool,

    flag_x: Option<String>,
    flag_c: Option<String>,

    flag_p: String,
    flag_s: String,
    flag_m: String,
    flag_n: bool,
}

fn write_base64_file<P: AsRef<Path>>(file: P, comment: &str, buf: &[u8]) -> Result<()> {
    let mut f = try!(OpenOptions::new().write(true).create_new(true).open(file));

    try!(write!(f, "{}", COMMENTHDR));
    try!(write!(f, "{}\n", comment));
    let out = base64::encode(buf);
    try!(write!(f, "{}\n", out));

    Ok(())
}

fn read_base64_file<P: AsRef<Path>>(file: P) -> Result<Vec<u8>> {
    let file_display = format!("{}", file.as_ref().display());
    let f = try!(File::open(file));
    let mut reader = BufReader::new(f);

    let mut comment_line = String::new();
    let len = try!(reader.read_line(&mut comment_line));

    if len == 0 || len < COMMENTHDRLEN || !comment_line.starts_with(COMMENTHDR) {
        return Err(format!("invalid comment in {}; must start with '{}'", file_display, COMMENTHDR).into());
    }

    if &comment_line[len-1..len] != "\n" {
        return Err(format!("missing new line after comment in {}", file_display).into());
    }

    if len > COMMENTHDRLEN + COMMENTMAXLEN {
        return Err("comment too long".into());
    }

    let mut base64_line = String::new();
    let len = try!(reader.read_line(&mut base64_line));

    if len == 0 {
        return Err(format!("missing line in {}", file_display).into());
    }

    if &base64_line[len-1..len] != "\n" {
        return Err(format!("missing new line after comment in {}", file_display).into());
    }

    let base64_line = &base64_line[0..len-1];

    let data = try!(base64::decode(base64_line)
                    .chain_err(|| format!("invalid base64 encoding in {}", file_display)));

    if &data[0..2] != PKGALG {
        return Err(format!("unsupported file {}", file_display).into());
    }

    Ok(data)
}

fn verify(pubkey_path: String, msg_path: String, signature_path: Option<String>) -> Result<()> {
    // TODO: Better error message?
    let serialized_pkey = try!(read_base64_file(&pubkey_path));
    let pkey = try!(PublicKey::from_buf(&serialized_pkey));

    let signature_path = match signature_path {
        Some(path) => path,
        None => format!("{}.sig", msg_path)
    };

    // TODO: Better error message?
    let serialized_signature = try!(read_base64_file(&signature_path));
    let signature = try!(Signature::from_buf(&serialized_signature));

    let mut msgfile = try!(File::open(&msg_path).chain_err(|| read_error(&msg_path)));
    let mut msg = vec![];
    try!(msgfile.read_to_end(&mut msg).chain_err(|| read_error(&msg_path)));

    if signature.keynum != pkey.keynum {
        return Err("signature verification failed: checked against wrong key".into());
    }

    if signature.verify(&msg, &pkey) {
        println!("Signature Verified");
        Ok(())
    } else {
        Err("signature verification failed".into())
    }
}

fn sign(seckey_path: String, msg_path: String, signature_path: Option<String>) -> Result<()> {
    let serialized_skey = try!(read_base64_file(&seckey_path));
    let mut skey = try!(PrivateKey::from_buf(&serialized_skey));

    let rounds = skey.kdfrounds;
    let xorkey = try!(kdf(&skey.salt, rounds, false, SECRETBYTES));

    for (prv, xor) in skey.seckey.iter_mut().zip(xorkey.iter()) {
        *prv = *prv ^ xor;
    }
    let skey = skey;

    let mut msgfile = try!(File::open(&msg_path)
                           .chain_err(|| read_error(&msg_path)).into());
    let mut msg = vec![];
    try!(msgfile.read_to_end(&mut msg).chain_err(|| read_error(&msg_path)));

    let signature_path = match signature_path {
        Some(path) => path,
        None => format!("{}.sig", msg_path)
    };

    let sig = try!(skey.sign(&msg).chain_err(|| "Failed to sign message"));

    let mut out = vec![];
    try!(sig.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let sig_comment = "signature from signify secret key";

    write_base64_file(&signature_path, sig_comment, &out)
        .chain_err(|| "Failed to write signature file")
}

fn read_password(prompt: &str) -> Result<String> {
    let mut stdout = std::io::stdout();
    try!(stdout.write_all(prompt.as_bytes()).chain_err(|| "Write to stdout failed"));
    try!(stdout.flush().chain_err(|| "Flushing stdout failed"));

    rpassword::read_password()
        .chain_err(|| "unable to read passphrase")
}

fn kdf(salt: &[u8], rounds: u32, confirm: bool, keylen: usize) -> Result<Vec<u8>> {
    let mut result = vec![0; keylen];
    if rounds == 0 {
        return Ok(result);
    }

    let passphrase = try!(read_password("passphrase: "));

    if confirm {
        let confirm_passphrase = try!(read_password("confirm passphrase: "));

        if passphrase != confirm_passphrase {
            return Err("passwords don't match".into());
        }
    }

    bcrypt_pbkdf(passphrase.as_bytes(), salt, rounds, &mut result);
    Ok(result)
}

fn generate(pubkey_path: String, privkey_path: String, comment: Option<String>, kdfrounds: u32) -> Result<()> {
    let comment = match comment {
        Some(s) => s,
        None    => "signify".into()
    };

    let mut keynum = [0; KEYNUMLEN];
    try!(SystemRandom.fill(&mut keynum).chain_err(|| "Can't fill keynum randomly"));

    let (_, keypair_bytes) = try!(Ed25519KeyPair::generate_serializable(&SystemRandom));
    let mut skey = keypair_bytes.private_key;
    let pkey = keypair_bytes.public_key;

    let mut salt = [0; 16];
    try!(SystemRandom.fill(&mut salt).chain_err(|| "Can't fill salt randomly"));

    let xorkey = try!(kdf(&salt, kdfrounds, true, SECRETBYTES));

    for (prv, xor) in skey.iter_mut().zip(xorkey.iter()) {
        *prv = *prv ^ xor;
    }

    // signify stores the extended key as the private key,
    // that is the 32 byte of the secret key, followed by the 32 byte of the public key,
    // summing up to 64 byte.
    //
    //  *ring* separates them, so we need to stick them together again.
    let mut complete_key = [0; 64];
    complete_key[0..32].copy_from_slice(&skey[0..32]);
    complete_key[32..].copy_from_slice(&pkey);

    // Store private key
    let digest = digest::digest(&digest::SHA512, &complete_key);
    let mut checksum = [0; 8];
    checksum.copy_from_slice(&digest.as_ref()[0..8]);

    let private_key = PrivateKey {
        pkgalg: PKGALG,
        kdfalg: KDFALG,
        kdfrounds: kdfrounds,
        salt: salt,
        checksum: checksum,
        keynum: keynum,
        seckey: complete_key,
    };

    let mut out = vec![];
    try!(private_key.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let priv_comment = format!("{} secret key", comment);
    try!(write_base64_file(&privkey_path, &priv_comment, &out)
        .chain_err(|| write_error(&privkey_path))
    );

    // Store public key
    let public_key = PublicKey::with_key_and_keynum(pkey, keynum);

    let mut out = vec![];
    try!(public_key.write(&mut out).chain_err(|| "Can't write to internal buffer"));

    let pub_comment = format!("{} public key", comment);
    write_base64_file(&pubkey_path, &pub_comment, &out)
        .chain_err(|| write_error(&pubkey_path))
}

fn human(res: Result<()>) {
    match res {
        Err(e) => {
            let mut it = e.iter();
            let e = it.next().unwrap(); // We have an error, so we can definitely unwrap it.
            let next_err = it.next();
            if let Some(next_err) = next_err {
                println!("{}: {}", e, next_err);
            } else {
                println!("{}", e);
            }

            process::exit(1);
        },
        Ok(()) => {}
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    if args.flag_V {
        human(verify(args.flag_p, args.flag_m, args.flag_x));
    } else if args.flag_G {
        let rounds = if args.flag_n { 0 } else { 42 };
        human(generate(args.flag_p, args.flag_s, args.flag_c, rounds));
    } else if args.flag_S {
        human(sign(args.flag_s, args.flag_m, args.flag_x));
    }
}
<|endoftext|>


<commit_before>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate time as crates_time;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::time;

use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;
use std::io::Write;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T: Clone> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }

    fn to_vec(&self) -> Vec<T> {
        let mut ret = Vec::new();
        let mut val = self;
        loop {
            ret.push(val.value.clone());
            if let Some(ref next) = val.next {
                val = next;
            } else {
                break;
            }
        }
        ret.reverse();
        ret
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
    size: u64,
    atime: u64,
    mtime: u64,
    ctime: u64,
    btime: u64,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        writeln!(io::stderr(), "TODO: {}", msg).expect("stderr");
    }

    fn raw<F: io::Read + ?Sized>(&self, mut file: &mut F) -> io::Result<()> {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        writeln!(stdout, "{:?} {} {} {} {} {}",
            self.path.to_vec(),
            self.size,
            self.atime, self.mtime, self.ctime, self.btime
        )?;

        io::copy(&mut file, &mut stdout).and_then(move |written|
            if written != self.size {
                Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("expecting to write {} but wrote {}", self.size, written)))
            } else {
                Ok(())
            })
    }

    fn from_file(path: &str, fd: &fs::File) -> io::Result<OutputTo> {
        let meta = fd.metadata()?;
        Ok(OutputTo {
            path: Node::head(path.to_string()),
            size: meta.len(),
            atime: meta.accessed().map(simple_time_sys)?,
            mtime: meta.modified().map(simple_time_sys)?,
            ctime: simple_time_ctime(&meta),
            btime: simple_time_btime(&meta)?,
        })
    }

    fn with_path(&self, path: &str) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path.to_string()),
            size: 0,
            atime: 0,
            mtime: 0,
            ctime: 0,
            btime: 0,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Status {
    Done,
    Rewind,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn simple_time(dur: time::Duration) -> u64 {
    dur.as_secs().checked_mul(1_000_000_000)
        .map_or(0, |nanos| nanos + dur.subsec_nanos() as u64)
}

fn simple_time_sys(val: time::SystemTime) -> u64 {
    val.duration_since(time::UNIX_EPOCH).map(simple_time).unwrap_or(0)
}

fn simple_time_tm(val: crates_time::Tm) -> u64 {
    let timespec = val.to_timespec();
    simple_time(time::Duration::new(timespec.sec as u64, timespec.nsec as u32))
}

fn simple_time_btime(val: &fs::Metadata) -> io::Result<u64> {
    match val.created() {
        Ok(time) => Ok(simple_time_sys(time)),
        // "Other" is how "unsupported" is represented here; ew.
        Err(ref e) if e.kind() == io::ErrorKind::Other => Ok(0),
        Err(other) => Err(other),
    }
}

// TODO: I really feel this should be exposed by Rust, without cfg.
fn simple_time_ctime(val: &fs::Metadata) -> u64 {
    #[cfg(linux)] {
        use std::os::linux::fs::MetadataExt;
        let ctime: i64 = val.st_ctime();
        if ctime <= 0 {
            0
        } else {
            ctime as u64
        }
    }

    #[cfg(not(linux))] {
        0
    }
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a, T: io::BufRead + ?Sized>(fd: &mut T) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 40
        && b'B' == header[0] && b'Z' == header[1]
        && b'h' == header[2] && 0x31 == header[3]
        && 0x41 == header[4] && 0x59 == header[5]
        && 0x26 == header[6] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

trait Tee {
    fn normal(&mut self) -> &mut io::BufRead;
    fn abort(&mut self) -> io::Result<&mut io::BufRead>;
}

struct TempFileTee<'a> {
    fp: Box<io::BufRead + 'a>,
    tmp: fs::File,
}

impl<'a> TempFileTee<'a> {
    fn new<U: 'a + io::Read>(from: U) -> io::Result<TempFileTee<'a>> {
        Ok(TempFileTee {
            fp: Box::new(io::BufReader::new(from)),
            tmp: tempfile()?
        })
    }
}

impl<'a> Tee for TempFileTee<'a> {
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }
    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        unimplemented!();
    }
}

struct BufReaderTee<T> {
    fp: Box<T>,
}

impl<T> BufReaderTee<T> {
    // TODO: return type?
    fn new<U: io::Read>(from: U) -> BufReaderTee<io::BufReader<U>> {
        BufReaderTee {
            fp: Box::new(io::BufReader::new(from))
        }
    }
}

impl<T> Tee for BufReaderTee<T>
    where T: io::BufRead + io::Seek
{
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }

    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        self.fp.seek(io::SeekFrom::Start(0))?;
        Ok(self.normal())
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack<'a>(mut fd: Box<Tee + 'a>, output: &OutputTo) -> io::Result<Status> {
    match identify(fd.normal())? {
        FileType::GZip => {
            let process_decoded = match gzip::Decoder::new(fd.normal()) {
                Ok(dec) => Ok(unpack(Box::new(TempFileTee::new(dec)?), output)?),
                Err(e) => Err(e),
            };

            match process_decoded {
                Ok(status) => Ok(status),
                Err(e) => {
                    output.warn(format!(
                        "thought we could unpack '{:?}' but we couldn't: {}",
                        output.path, e));
                    output.raw(fd.abort()?)?;
                    Ok(Status::Done)
                }
            }
        },
//        FileType::Xz => {
//            unpack_or_raw(fd, output,
//                |fd| Ok(Box::new(TempFileTee::new(xz2::bufread::XzDecoder::new(fd)))))
//        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd.normal());
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier());
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd.normal());
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8"));
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Zip => {
            let mut temp = tempfile()?;
            io::copy(fd.normal(), &mut temp)?;
            let mut zip = zip::ZipArchive::new(temp)?;

            for i in 0..zip.len() {

                // well, this has gone rather poorly
                let mut new_output = output.with_path(zip.by_index(i)?.name());

                let file_result = {
                    let entry = zip.by_index(i)?;

                    new_output.size = entry.size();
                    new_output.mtime = simple_time_tm(entry.last_modified());
                    let reader = Box::new(TempFileTee::new(entry)?);
                    unpack(reader, &new_output)?
                };
                match file_result {
                    Status::Done => (),
                    Status::Rewind => {
                        // TODO: update output? ? handling? ???
                        new_output.raw(&mut zip.by_index(i)?)?
                    },
                };
            }

            Ok(Status::Done)
        },

        // TODO: unimplemented!()
        _ => {
            Ok(Status::Rewind)
        },
    }
}

fn output_raw(file: &mut fs::File, output: &OutputTo) -> io::Result<()> {
    file.seek(io::SeekFrom::Start(0))?;
    output.raw(file)?;
    Ok(())
}

fn process_real_path(path: &str) -> io::Result<()> {
    let mut file = fs::File::open(path).unwrap();
    let output = OutputTo::from_file(
        path,
        &file
    )?;

    let attempt = {
//        let read = BufReaderTee::new::<BufReaderTee<io::BufReader<fs::File>>>(file);
        let read = TempFileTee::new(&file)?;
        unpack(Box::new(read), &output)
    };

    match attempt {
        Ok(Status::Done) => Ok(()),
        Ok(Status::Rewind) => output_raw(&mut file, &output),
        Err(e) => Err(e)
    }
}

fn real_main() -> u8 {

    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();

    for path in matches.values_of("INPUT").unwrap() {
        if let Err(e) = process_real_path(path) {
            if let Err(_) = writeln!(io::stderr(), "fatal: processing '{}': {}", path, e) {
                return 6;
            }

            return 4;
        }
    }
    return 0;
}

fn main() {
    std::process::exit(real_main() as i32)
}
<commit_msg>BufReaderTee can actually be used there<commit_after>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate time as crates_time;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::time;

use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;
use std::io::Write;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T: Clone> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }

    fn to_vec(&self) -> Vec<T> {
        let mut ret = Vec::new();
        let mut val = self;
        loop {
            ret.push(val.value.clone());
            if let Some(ref next) = val.next {
                val = next;
            } else {
                break;
            }
        }
        ret.reverse();
        ret
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
    size: u64,
    atime: u64,
    mtime: u64,
    ctime: u64,
    btime: u64,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        writeln!(io::stderr(), "TODO: {}", msg).expect("stderr");
    }

    fn raw<F: io::Read + ?Sized>(&self, mut file: &mut F) -> io::Result<()> {
        let stdout = io::stdout();
        let mut stdout = stdout.lock();
        writeln!(stdout, "{:?} {} {} {} {} {}",
            self.path.to_vec(),
            self.size,
            self.atime, self.mtime, self.ctime, self.btime
        )?;

        io::copy(&mut file, &mut stdout).and_then(move |written|
            if written != self.size {
                Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    format!("expecting to write {} but wrote {}", self.size, written)))
            } else {
                Ok(())
            })
    }

    fn from_file(path: &str, fd: &fs::File) -> io::Result<OutputTo> {
        let meta = fd.metadata()?;
        Ok(OutputTo {
            path: Node::head(path.to_string()),
            size: meta.len(),
            atime: meta.accessed().map(simple_time_sys)?,
            mtime: meta.modified().map(simple_time_sys)?,
            ctime: simple_time_ctime(&meta),
            btime: simple_time_btime(&meta)?,
        })
    }

    fn with_path(&self, path: &str) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path.to_string()),
            size: 0,
            atime: 0,
            mtime: 0,
            ctime: 0,
            btime: 0,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Status {
    Done,
    Rewind,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn simple_time(dur: time::Duration) -> u64 {
    dur.as_secs().checked_mul(1_000_000_000)
        .map_or(0, |nanos| nanos + dur.subsec_nanos() as u64)
}

fn simple_time_sys(val: time::SystemTime) -> u64 {
    val.duration_since(time::UNIX_EPOCH).map(simple_time).unwrap_or(0)
}

fn simple_time_tm(val: crates_time::Tm) -> u64 {
    let timespec = val.to_timespec();
    simple_time(time::Duration::new(timespec.sec as u64, timespec.nsec as u32))
}

fn simple_time_btime(val: &fs::Metadata) -> io::Result<u64> {
    match val.created() {
        Ok(time) => Ok(simple_time_sys(time)),
        // "Other" is how "unsupported" is represented here; ew.
        Err(ref e) if e.kind() == io::ErrorKind::Other => Ok(0),
        Err(other) => Err(other),
    }
}

// TODO: I really feel this should be exposed by Rust, without cfg.
fn simple_time_ctime(val: &fs::Metadata) -> u64 {
    #[cfg(linux)] {
        use std::os::linux::fs::MetadataExt;
        let ctime: i64 = val.st_ctime();
        if ctime <= 0 {
            0
        } else {
            ctime as u64
        }
    }

    #[cfg(not(linux))] {
        0
    }
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a, T: io::BufRead + ?Sized>(fd: &mut T) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 40
        && b'B' == header[0] && b'Z' == header[1]
        && b'h' == header[2] && 0x31 == header[3]
        && 0x41 == header[4] && 0x59 == header[5]
        && 0x26 == header[6] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

trait Tee {
    fn normal(&mut self) -> &mut io::BufRead;
    fn abort(&mut self) -> io::Result<&mut io::BufRead>;
}

struct TempFileTee<'a> {
    fp: Box<io::BufRead + 'a>,
    tmp: fs::File,
}

impl<'a> TempFileTee<'a> {
    fn new<U: 'a + io::Read>(from: U) -> io::Result<TempFileTee<'a>> {
        Ok(TempFileTee {
            fp: Box::new(io::BufReader::new(from)),
            tmp: tempfile()?
        })
    }
}

impl<'a> Tee for TempFileTee<'a> {
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }
    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        unimplemented!();
    }
}

struct BufReaderTee<T> {
    fp: Box<T>,
}

impl<U: io::Read> BufReaderTee<io::BufReader<U>> {
    fn new(from: U) -> Self {
        BufReaderTee {
            fp: Box::new(io::BufReader::new(from))
        }
    }
}

impl<T> Tee for BufReaderTee<T>
    where T: io::BufRead + io::Seek
{
    fn normal(&mut self) -> &mut io::BufRead {
        &mut self.fp
    }

    fn abort(&mut self) -> io::Result<&mut io::BufRead> {
        self.fp.seek(io::SeekFrom::Start(0))?;
        Ok(self.normal())
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack<'a>(mut fd: Box<Tee + 'a>, output: &OutputTo) -> io::Result<Status> {
    match identify(fd.normal())? {
        FileType::GZip => {
            let process_decoded = match gzip::Decoder::new(fd.normal()) {
                Ok(dec) => Ok(unpack(Box::new(TempFileTee::new(dec)?), output)?),
                Err(e) => Err(e),
            };

            match process_decoded {
                Ok(status) => Ok(status),
                Err(e) => {
                    output.warn(format!(
                        "thought we could unpack '{:?}' but we couldn't: {}",
                        output.path, e));
                    output.raw(fd.abort()?)?;
                    Ok(Status::Done)
                }
            }
        },
//        FileType::Xz => {
//            unpack_or_raw(fd, output,
//                |fd| Ok(Box::new(TempFileTee::new(xz2::bufread::XzDecoder::new(fd)))))
//        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd.normal());
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier());
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd.normal());
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8"));
                unpack(Box::new(TempFileTee::new(entry)?), &new_output)?;
            }
            Ok(Status::Done)
        },
        FileType::Zip => {
            let mut temp = tempfile()?;
            io::copy(fd.normal(), &mut temp)?;
            let mut zip = zip::ZipArchive::new(temp)?;

            for i in 0..zip.len() {

                // well, this has gone rather poorly
                let mut new_output = output.with_path(zip.by_index(i)?.name());

                let file_result = {
                    let entry = zip.by_index(i)?;

                    new_output.size = entry.size();
                    new_output.mtime = simple_time_tm(entry.last_modified());
                    let reader = Box::new(TempFileTee::new(entry)?);
                    unpack(reader, &new_output)?
                };
                match file_result {
                    Status::Done => (),
                    Status::Rewind => {
                        // TODO: update output? ? handling? ???
                        new_output.raw(&mut zip.by_index(i)?)?
                    },
                };
            }

            Ok(Status::Done)
        },

        // TODO: unimplemented!()
        _ => {
            Ok(Status::Rewind)
        },
    }
}

fn output_raw(file: &mut fs::File, output: &OutputTo) -> io::Result<()> {
    file.seek(io::SeekFrom::Start(0))?;
    output.raw(file)?;
    Ok(())
}

fn process_real_path(path: &str) -> io::Result<()> {
    let mut file = fs::File::open(path).unwrap();
    let output = OutputTo::from_file(
        path,
        &file
    )?;

    let attempt = unpack(Box::new(BufReaderTee::new(&file)), &output);

    match attempt {
        Ok(Status::Done) => Ok(()),
        Ok(Status::Rewind) => output_raw(&mut file, &output),
        Err(e) => Err(e)
    }
}

fn real_main() -> u8 {

    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();

    for path in matches.values_of("INPUT").unwrap() {
        if let Err(e) = process_real_path(path) {
            if let Err(_) = writeln!(io::stderr(), "fatal: processing '{}': {}", path, e) {
                return 6;
            }

            return 4;
        }
    }
    return 0;
}

fn main() {
    std::process::exit(real_main() as i32)
}
<|endoftext|>