<commit_before>use std::fmt::{Display, Error, Formatter};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BinOp {
    And,
    Ge,
    Gt,
    Eql,       // ==
    EqlStrict, // ===
    Le,
    Lt,
    Minus,
    Neq,       // !=
    NeqStrict, // !==
    Or,
    Plus,
    Slash,
    Star,
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precedence {
    Const = 110,
    Sign = 100,
    Inc = 90,
    Mult = 70,
    Add = 60,
    Equality = 50,
    And = 40,
    Or = 30,
}

impl BinOp {
    pub fn precedence(&self) -> Precedence {
        match *self {
            BinOp::And => Precedence::And,
            BinOp::Ge | BinOp::Gt | BinOp::Eql | BinOp::Le | BinOp::Lt |
                BinOp::Neq | BinOp::EqlStrict | BinOp::NeqStrict
                => Precedence::Equality,
            BinOp::Or => Precedence::Or,
            BinOp::Minus | BinOp::Plus => Precedence::Add,
            BinOp::Slash | BinOp::Star => Precedence::Mult,
        }
    }

    pub fn is_commutative(&self) -> bool {
        match *self {
            BinOp::Minus | BinOp::Slash => false,
            _ => true
        }
    }
}

impl Display for BinOp {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        match *self {
            BinOp::And => write!(fmt, "&&"),
            BinOp::Ge => write!(fmt, ">="),
            BinOp::Gt => write!(fmt, ">"),
            BinOp::Eql => write!(fmt, "=="),
            BinOp::EqlStrict => write!(fmt, "==="),
            BinOp::Le => write!(fmt, "<="),
            BinOp::Lt => write!(fmt, "<"),
            BinOp::Minus => write!(fmt, "-"),
            BinOp::Neq => write!(fmt, "!="),
            BinOp::NeqStrict => write!(fmt, "!=="),
            BinOp::Or => write!(fmt, "||"),
            BinOp::Plus => write!(fmt, "+"),
            BinOp::Slash => write!(fmt, "/"),
            BinOp::Star => write!(fmt, "*"),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Exp {
    Array(Vec<Box<Exp>>),
    BinExp(Box<Exp>, BinOp, Box<Exp>),
    Bool(bool),
    Call(Box<Exp>, Vec<Box<Exp>>),
    Defun(Option<String>, Vec<String>, Vec<Stmt>),
    Float(f64),
    InstanceVar(Box<Exp>, String),
    KeyAccessor(Box<Exp>, Box<Exp>),
    Neg(Box<Exp>),
    Null,
    NewObject(Box<Exp>, Vec<Box<Exp>>),
    Object(Vec<(String, Box<Exp>)>),
    Pos(Box<Exp>),
    PostDec(Box<Exp>),
    PostInc(Box<Exp>),
    PreDec(Box<Exp>),
    PreInc(Box<Exp>),
    TypeOf(Box<Exp>),
    Str(String),
    Undefined,
    Var(String),
}

impl Exp {
    pub fn precedence(&self) -> Precedence {
        match *self {
            Exp::BinExp(_, ref o, _) => o.precedence(),
            Exp::Neg(_) | Exp::Pos(_) => Precedence::Sign,
            Exp::PostDec(_) | Exp::PostInc(_) | Exp::PreDec(_) | Exp::PreInc(_) => Precedence::Inc,
            _ => Precedence::Const
        }
    }
}

macro_rules! group {
    ($e:expr, $p:expr) => {
        if $e.precedence() < $p {
            format!("({})", $e)
        } else {
            format!("{}", $e)
        }
    }
}

impl Exp {
    fn fmt_helper(&self, mut fmt: &mut Formatter, indent_level: i32) -> Result<(), Error> {
        macro_rules! stmt_block {
            ($stmt:expr) => {
                for s in $stmt {
                    try!(s.fmt_helper(&mut fmt, indent_level + 2))
                }
            }
        }

        match *self {
            Exp::Array(ref vec) => {
                try!(write!(fmt, "["));

                for (i, elem) in vec.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", elem));
                }

                write!(fmt, "]")
            }
            Exp::BinExp(ref e1, ref o, ref e2) => {
                let prec = self.precedence();

                // Put grouping parentheses if the left subexpression has a lower-precedence
                // operator, e.g. (1 + 2) * 3
                let left = if prec > e1.precedence() {
                    format!("({})", e1)
                } else {
                    format!("{}", e1)
                };

                let right_prec = e2.precedence();

                // Put grouping parentheses around the right subexpression if it has a
                // lower-precedence operator,  __OR__ if `o` is not commutative and the precedence
                // is the same, e.g. (1 + 2) * 3 __OR__ 1 - (2 + 3)
                let right = if prec > right_prec || (!o.is_commutative() && prec == right_prec) {
                    format!("({})", e2)
                } else {
                    format!("{}", e2)
                };

                write!(fmt, "{} {} {}", left, o, right)
            }
            Exp::Bool(b) => write!(fmt, "{}", b),
            Exp::Call(ref func, ref args) => {
                try!(write!(fmt, "{}(", func));

                for (i, arg) in args.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", arg));
                }

                write!(fmt, ")")
            }
            Exp::Defun(ref opt, ref params, ref body) => {
                try!(write!(fmt, "function"));

                if let &Some(ref func) = opt {
                    try!(write!(fmt, " {}", func));
                }

                try!(write!(fmt, "("));

                for (i, param) in params.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", param));
                }

                try!(write!(fmt, ") {{\n"));
                stmt_block!(body);

                let indent : String = (0..indent_level).map(|_| " ").collect();

                write!(fmt, "{}}}", indent)
            }
            Exp::Float(f) => write!(fmt, "{}", f),
            Exp::InstanceVar(ref obj, ref name) => write!(fmt, "{}.{}", obj, name),
            Exp::KeyAccessor(ref obj, ref key) => write!(fmt, "{}[{}]", obj, key),
            Exp::Neg(ref e) => write!(fmt, "-{}", group!(e, Precedence::Sign)),
            Exp::NewObject(ref name, ref args) => {
                try!(write!(fmt, "new {}(", name));

                for (i, arg) in args.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", arg))
                }

                write!(fmt, ")")
            }
            Exp::Null => write!(fmt, "null"),
            Exp::Object(ref properties) => {
                if properties.is_empty() {
                    return write!(fmt, "{{}}");
                }

                try!(writeln!(fmt, "{{"));

                let indent : String = (0..indent_level + 2).map(|_| " ").collect();

                for (i, &(ref name, ref prop)) in properties.iter().enumerate() {
                    if i != 0 {
                        try!(writeln!(fmt, ","));
                    }

                    try!(write!(fmt, "{}{}: ", indent, name));
                    try!(prop.fmt_helper(&mut fmt, indent_level + 2));
                }

                write!(fmt, "\n}}")
            }
            Exp::Pos(ref e) => write!(fmt, "+{}", group!(e, Precedence::Sign)),
            Exp::PostDec(ref e) => write!(fmt, "{}--", group!(e, Precedence::Inc)),
            Exp::PostInc(ref e) => write!(fmt, "{}++", group!(e, Precedence::Inc)),
            Exp::PreDec(ref e) => write!(fmt, "--{}", group!(e, Precedence::Inc)),
            Exp::PreInc(ref e) => write!(fmt, "++{}", group!(e, Precedence::Inc)),
            Exp::TypeOf(ref e) => write!(fmt, "typeof {}", e),
            Exp::Str(ref s) => write!(fmt, "\"{}\"", s),
            Exp::Undefined => write!(fmt, "undefined"),
            Exp::Var(ref v) => write!(fmt, "{}", v),
        }
    }
}

impl Display for Exp {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        self.fmt_helper(&mut fmt, 0)
    }
}

#[derive(Clone, Debug)]
pub enum Stmt {
    Assign(Exp, Exp),
    BareExp(Exp),
    Decl(String, Exp),
    Empty,
    If(Exp, Vec<Stmt>, Vec<Stmt>),
    Ret(Exp),
    Seq(Box<Stmt>, Box<Stmt>),
    // try block, catch variable, catch block, finally block
    Try(Vec<Stmt>, String, Vec<Stmt>, Vec<Stmt>),
    Throw(Box<Exp>),
    While(Exp, Vec<Stmt>),
}

impl Stmt {
    pub fn fmt_helper(&self, mut fmt: &mut Formatter, indent_level: i32) -> Result<(), Error> {
        macro_rules! stmt_block {
            ($stmt:expr) => {
                for s in $stmt {
                    try!(s.fmt_helper(&mut fmt, indent_level + 2))
                }
            }
        }

        macro_rules! indented_stmt {
            ($s:expr) => {
                try!($s.fmt_helper(&mut fmt, indent_level + 2))
            }
        }

        macro_rules! exp_semi {
            ($exp:expr) => {{
                try!($exp.fmt_helper(&mut fmt, indent_level));
                writeln!(fmt, ";")
            }}
        }

        let indent : String = (0..indent_level).map(|_| " ").collect();

        match *self {
            Stmt::Assign(ref v, ref exp) => {
                try!(write!(fmt, "{}{} = ", indent, v));
                exp_semi!(exp)
            }
            Stmt::BareExp(ref exp) => {
                try!(write!(fmt, "{}", indent));
                exp_semi!(exp)
            }
            Stmt::Decl(ref v, ref exp) => {
                try!(write!(fmt, "{}var {} = ", indent, v));
                exp_semi!(exp)
            }
            Stmt::Empty => Ok(()),
            Stmt::If(ref e, ref s, ref els) => {
                try!(write!(fmt, "{}if (", indent));
                try!(e.fmt_helper(&mut fmt, indent_level + 2));
                try!(writeln!(fmt, ") {{\n"));
                stmt_block!(s);

                if els.len() > 0 {
                    stmt_block!(s);
                }

                //if let &Some(ref stmt) = els {
                //    try!(write!(fmt, "{}else {{\n", indent));
                //    indented_stmt!(stmt);
                //    try!(write!(fmt, "{}}}\n", indent));
                //}

                Ok(())
            }
            Stmt::Ret(ref e) => {
                try!(write!(fmt, "{}return ", indent));
                exp_semi!(e)
            }
            Stmt::Seq(ref s1, ref s2) => {
                try!(s1.fmt_helper(&mut fmt, indent_level));
                s2.fmt_helper(&mut fmt, indent_level)
            }
            Stmt::Throw(ref e) => {
                write!(fmt, "{}throw {}", indent, e)
            }
            Stmt::Try(ref stmt, ref catch_var, ref catch_block, ref finally_block) => {
                try!(write!(fmt, "{}try {{\n", indent));
                stmt_block!(stmt);
                try!(write!(fmt, "{}}}\n", indent));

                if catch_block.len() > 0 {
                    try!(write!(fmt, "{}catch ({}) {{\n", catch_var, indent));
                    stmt_block!(catch_block);
                    try!(write!(fmt, "{}}}\n", indent));
                }

                if finally_block.len() > 0 {
                    try!(write!(fmt, "{}finally {{\n", indent));
                    stmt_block!(finally_block);
                    try!(write!(fmt, "{}}}\n", indent));
                }

                Ok(())
            }
            Stmt::While(ref exp, ref stmt) => {
                try!(write!(fmt, "{}while ({}) {{\n", indent, exp));
                stmt_block!(stmt);
                write!(fmt, "{}}}\n", indent)
            }
        }
    }
}

impl Display for Stmt {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        self.fmt_helper(&mut fmt, 0)
    }
}
<commit_msg>More operators<commit_after>use std::fmt::{Display, Error, Formatter};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BinOp {
    Ge,
    Gt,
    Eql,       // ==
    EqlStrict, // ===
    Neq,       // !=
    NeqStrict, // !==
    Le,
    Lt,

    And,
    Or,

    BitOr,
    BitXor,
    BitAnd,

    ShiftLeft,
    ShiftRight,
    ShiftRightUnsigned,

    Minus,
    Plus,
    Slash,
    Star,
    Exponent,
    Mod,
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precedence {
    Const = 110,
    Sign = 100,
    Inc = 90,
    Mult = 70,
    Add = 60,
    Shift = 52,
    Cmp = 51,
    Equality = 50,
    BitAnd = 43,
    BitXor = 42,
    BitOr = 41,
    And = 40,
    Or = 30,
}

impl BinOp {
    pub fn precedence(&self) -> Precedence {
        match *self {
            BinOp::And => Precedence::And,
            BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt => Precedence::Cmp,
            BinOp::Eql | BinOp::Neq | BinOp::EqlStrict | BinOp::NeqStrict => Precedence::Equality,
            BinOp::Or => Precedence::Or,
            BinOp::BitOr => Precedence::BitOr,
            BinOp::BitXor => Precedence::BitXor,
            BinOp::BitAnd => Precedence::BitAnd,
            BinOp::Minus | BinOp::Plus => Precedence::Add,
            BinOp::Slash | BinOp::Star | BinOp::Exponent | BinOp::Mod => Precedence::Mult,
            BinOp::ShiftLeft | BinOp::ShiftRight | BinOp::ShiftRightUnsigned => Precedence::Shift,
        }
    }

    pub fn is_commutative(&self) -> bool {
        match *self {
            BinOp::Minus | BinOp::Slash => false,
            _ => true
        }
    }
}

impl Display for BinOp {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        match *self {
            BinOp::And => write!(fmt, "&&"),
            BinOp::Ge => write!(fmt, ">="),
            BinOp::Gt => write!(fmt, ">"),
            BinOp::Eql => write!(fmt, "=="),
            BinOp::EqlStrict => write!(fmt, "==="),
            BinOp::Le => write!(fmt, "<="),
            BinOp::Lt => write!(fmt, "<"),
            BinOp::Minus => write!(fmt, "-"),
            BinOp::Neq => write!(fmt, "!="),
            BinOp::NeqStrict => write!(fmt, "!=="),
            BinOp::Or => write!(fmt, "||"),
            BinOp::Plus => write!(fmt, "+"),
            BinOp::Slash => write!(fmt, "/"),
            BinOp::Star => write!(fmt, "*"),
            BinOp::Exponent => write!(fmt, "**"),
            BinOp::Mod => write!(fmt, "%"),
            BinOp::BitXor => write!(fmt, "^"),
            BinOp::BitOr => write!(fmt, "|"),
            BinOp::BitAnd => write!(fmt, "&"),
            BinOp::ShiftLeft => write!(fmt, "<<"),
            BinOp::ShiftRight => write!(fmt, ">>"),
            BinOp::ShiftRightUnsigned => write!(fmt, ">>>"),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Exp {
    Array(Vec<Box<Exp>>),
    BinExp(Box<Exp>, BinOp, Box<Exp>),
    Bool(bool),
    Call(Box<Exp>, Vec<Box<Exp>>),
    Defun(Option<String>, Vec<String>, Vec<Stmt>),
    Float(f64),
    InstanceVar(Box<Exp>, String),
    KeyAccessor(Box<Exp>, Box<Exp>),
    Neg(Box<Exp>),
    Null,
    NewObject(Box<Exp>, Vec<Box<Exp>>),
    Object(Vec<(String, Box<Exp>)>),
    Pos(Box<Exp>),
    PostDec(Box<Exp>),
    PostInc(Box<Exp>),
    PreDec(Box<Exp>),
    PreInc(Box<Exp>),
    TypeOf(Box<Exp>),
    Str(String),
    Undefined,
    Var(String),
}

impl Exp {
    pub fn precedence(&self) -> Precedence {
        match *self {
            Exp::BinExp(_, ref o, _) => o.precedence(),
            Exp::Neg(_) | Exp::Pos(_) => Precedence::Sign,
            Exp::PostDec(_) | Exp::PostInc(_) | Exp::PreDec(_) | Exp::PreInc(_) => Precedence::Inc,
            _ => Precedence::Const
        }
    }
}

macro_rules! group {
    ($e:expr, $p:expr) => {
        if $e.precedence() < $p {
            format!("({})", $e)
        } else {
            format!("{}", $e)
        }
    }
}

impl Exp {
    fn fmt_helper(&self, mut fmt: &mut Formatter, indent_level: i32) -> Result<(), Error> {
        macro_rules! stmt_block {
            ($stmt:expr) => {
                for s in $stmt {
                    try!(s.fmt_helper(&mut fmt, indent_level + 2))
                }
            }
        }

        match *self {
            Exp::Array(ref vec) => {
                try!(write!(fmt, "["));

                for (i, elem) in vec.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", elem));
                }

                write!(fmt, "]")
            }
            Exp::BinExp(ref e1, ref o, ref e2) => {
                let prec = self.precedence();

                // Put grouping parentheses if the left subexpression has a lower-precedence
                // operator, e.g. (1 + 2) * 3
                let left = if prec > e1.precedence() {
                    format!("({})", e1)
                } else {
                    format!("{}", e1)
                };

                let right_prec = e2.precedence();

                // Put grouping parentheses around the right subexpression if it has a
                // lower-precedence operator,  __OR__ if `o` is not commutative and the precedence
                // is the same, e.g. (1 + 2) * 3 __OR__ 1 - (2 + 3)
                let right = if prec > right_prec || (!o.is_commutative() && prec == right_prec) {
                    format!("({})", e2)
                } else {
                    format!("{}", e2)
                };

                write!(fmt, "{} {} {}", left, o, right)
            }
            Exp::Bool(b) => write!(fmt, "{}", b),
            Exp::Call(ref func, ref args) => {
                try!(write!(fmt, "{}(", func));

                for (i, arg) in args.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", arg));
                }

                write!(fmt, ")")
            }
            Exp::Defun(ref opt, ref params, ref body) => {
                try!(write!(fmt, "function"));

                if let &Some(ref func) = opt {
                    try!(write!(fmt, " {}", func));
                }

                try!(write!(fmt, "("));

                for (i, param) in params.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", param));
                }

                try!(write!(fmt, ") {{\n"));
                stmt_block!(body);

                let indent : String = (0..indent_level).map(|_| " ").collect();

                write!(fmt, "{}}}", indent)
            }
            Exp::Float(f) => write!(fmt, "{}", f),
            Exp::InstanceVar(ref obj, ref name) => write!(fmt, "{}.{}", obj, name),
            Exp::KeyAccessor(ref obj, ref key) => write!(fmt, "{}[{}]", obj, key),
            Exp::Neg(ref e) => write!(fmt, "-{}", group!(e, Precedence::Sign)),
            Exp::NewObject(ref name, ref args) => {
                try!(write!(fmt, "new {}(", name));

                for (i, arg) in args.iter().enumerate() {
                    if i != 0 {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", arg))
                }

                write!(fmt, ")")
            }
            Exp::Null => write!(fmt, "null"),
            Exp::Object(ref properties) => {
                if properties.is_empty() {
                    return write!(fmt, "{{}}");
                }

                try!(writeln!(fmt, "{{"));

                let indent : String = (0..indent_level + 2).map(|_| " ").collect();

                for (i, &(ref name, ref prop)) in properties.iter().enumerate() {
                    if i != 0 {
                        try!(writeln!(fmt, ","));
                    }

                    try!(write!(fmt, "{}{}: ", indent, name));
                    try!(prop.fmt_helper(&mut fmt, indent_level + 2));
                }

                write!(fmt, "\n}}")
            }
            Exp::Pos(ref e) => write!(fmt, "+{}", group!(e, Precedence::Sign)),
            Exp::PostDec(ref e) => write!(fmt, "{}--", group!(e, Precedence::Inc)),
            Exp::PostInc(ref e) => write!(fmt, "{}++", group!(e, Precedence::Inc)),
            Exp::PreDec(ref e) => write!(fmt, "--{}", group!(e, Precedence::Inc)),
            Exp::PreInc(ref e) => write!(fmt, "++{}", group!(e, Precedence::Inc)),
            Exp::TypeOf(ref e) => write!(fmt, "typeof {}", e),
            Exp::Str(ref s) => write!(fmt, "\"{}\"", s),
            Exp::Undefined => write!(fmt, "undefined"),
            Exp::Var(ref v) => write!(fmt, "{}", v),
        }
    }
}

impl Display for Exp {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        self.fmt_helper(&mut fmt, 0)
    }
}

#[derive(Clone, Debug)]
pub enum Stmt {
    Assign(Exp, Exp),
    BareExp(Exp),
    Decl(String, Exp),
    Empty,
    If(Exp, Vec<Stmt>, Vec<Stmt>),
    Ret(Exp),
    Seq(Box<Stmt>, Box<Stmt>),
    // try block, catch variable, catch block, finally block
    Try(Vec<Stmt>, String, Vec<Stmt>, Vec<Stmt>),
    Throw(Box<Exp>),
    While(Exp, Vec<Stmt>),
}

impl Stmt {
    pub fn fmt_helper(&self, mut fmt: &mut Formatter, indent_level: i32) -> Result<(), Error> {
        macro_rules! stmt_block {
            ($stmt:expr) => {
                for s in $stmt {
                    try!(s.fmt_helper(&mut fmt, indent_level + 2))
                }
            }
        }

        macro_rules! indented_stmt {
            ($s:expr) => {
                try!($s.fmt_helper(&mut fmt, indent_level + 2))
            }
        }

        macro_rules! exp_semi {
            ($exp:expr) => {{
                try!($exp.fmt_helper(&mut fmt, indent_level));
                writeln!(fmt, ";")
            }}
        }

        let indent : String = (0..indent_level).map(|_| " ").collect();

        match *self {
            Stmt::Assign(ref v, ref exp) => {
                try!(write!(fmt, "{}{} = ", indent, v));
                exp_semi!(exp)
            }
            Stmt::BareExp(ref exp) => {
                try!(write!(fmt, "{}", indent));
                exp_semi!(exp)
            }
            Stmt::Decl(ref v, ref exp) => {
                try!(write!(fmt, "{}var {} = ", indent, v));
                exp_semi!(exp)
            }
            Stmt::Empty => Ok(()),
            Stmt::If(ref e, ref s, ref els) => {
                try!(write!(fmt, "{}if (", indent));
                try!(e.fmt_helper(&mut fmt, indent_level + 2));
                try!(writeln!(fmt, ") {{\n"));
                stmt_block!(s);

                if els.len() > 0 {
                    stmt_block!(s);
                }

                //if let &Some(ref stmt) = els {
                //    try!(write!(fmt, "{}else {{\n", indent));
                //    indented_stmt!(stmt);
                //    try!(write!(fmt, "{}}}\n", indent));
                //}

                Ok(())
            }
            Stmt::Ret(ref e) => {
                try!(write!(fmt, "{}return ", indent));
                exp_semi!(e)
            }
            Stmt::Seq(ref s1, ref s2) => {
                try!(s1.fmt_helper(&mut fmt, indent_level));
                s2.fmt_helper(&mut fmt, indent_level)
            }
            Stmt::Throw(ref e) => {
                write!(fmt, "{}throw {}", indent, e)
            }
            Stmt::Try(ref stmt, ref catch_var, ref catch_block, ref finally_block) => {
                try!(write!(fmt, "{}try {{\n", indent));
                stmt_block!(stmt);
                try!(write!(fmt, "{}}}\n", indent));

                if catch_block.len() > 0 {
                    try!(write!(fmt, "{}catch ({}) {{\n", catch_var, indent));
                    stmt_block!(catch_block);
                    try!(write!(fmt, "{}}}\n", indent));
                }

                if finally_block.len() > 0 {
                    try!(write!(fmt, "{}finally {{\n", indent));
                    stmt_block!(finally_block);
                    try!(write!(fmt, "{}}}\n", indent));
                }

                Ok(())
            }
            Stmt::While(ref exp, ref stmt) => {
                try!(write!(fmt, "{}while ({}) {{\n", indent, exp));
                stmt_block!(stmt);
                write!(fmt, "{}}}\n", indent)
            }
        }
    }
}

impl Display for Stmt {
    fn fmt(&self, mut fmt: &mut Formatter) -> Result<(), Error> {
        self.fmt_helper(&mut fmt, 0)
    }
}
<|endoftext|>


<commit_before>use htslib;
use std::ffi::c_str_to_bytes;
use std::slice::from_raw_parts;
use std::ffi::CString;
use std::mem::copy_lifetime;

#[derive(Debug)]
pub enum Aux<'a> {
    Integer(i32),
    String(&'a [u8]),
    Float(f64),
    Char(u8),
}

impl<'a> Aux<'a> {
    pub fn string(&self) -> &'a [u8] {
        match *self {
            Aux::String(x) => x,
            _ => panic!("not a string"),
        }
    }

    pub fn float(&self) -> f64 {
        match *self {
            Aux::Float(x) => x,
            _ => panic!("not a float"),
        }
    }

    pub fn integer(&self) -> i32 {
        match *self {
            Aux::Integer(x) => x,
            _ => panic!("not an integer"),
        }
    }

    pub fn char(&self) -> u8 {
        match *self {
            Aux::Char(x) => x,
            _ => panic!("not a character"),
        }
    }
}

pub struct Record<'a> {
    b: &'a htslib::bam1_t,
    data: &'a [u8],
}

impl<'a> Record<'a>{
    pub fn new(b: &'a htslib::bam1_t) -> Record {
        Record { b: b, data: unsafe { from_raw_parts((*b).data, b.l_data as usize) } }
    }

    fn qname_len(&self) -> usize {
        self.b.core.l_qname as usize
    }

    pub fn qname(&self) -> &[u8] {
        self.data[0..self.qname_len()].as_slice()
    }

    fn cigar_len(&self) -> usize {
        self.b.core.n_cigar as usize
    }

    pub fn bam_get_cigar(&self) -> &[u32] {
        let x = self.b.core.l_qname as usize;
        unsafe { from_raw_parts(self.data[self.qname_len()..].as_ptr() as *const u32, self.cigar_len()) }
    }

    fn seq_len(&self) -> usize {
        self.b.core.l_qseq as usize
    }

    pub fn seq(&self) -> &[u8] {
        self.data[self.qname_len() + self.cigar_len()*4..][..self.seq_len()].as_slice()
    }

    pub fn qual(&self) -> &[u8] {
        self.data[self.qname_len() + self.cigar_len()*4 + (self.seq_len()+1)/2..][..self.seq_len()].as_slice()
    }

    pub fn aux(&self, name: &[u8]) -> Result<Aux, &str> {
        let aux = unsafe { htslib::bam_aux_get(self.b, name.as_ptr() as *mut i8 ) };
        unsafe { println!("{}", *aux); }

        unsafe {
            match *aux {
                b'c'|b'C'|b's'|b'S'|b'i'|b'I' => Ok(Aux::Integer(htslib::bam_aux2i(aux))),
                b'f'|b'd' => Ok(Aux::Float(htslib::bam_aux2f(aux))),
                b'A' => Ok(Aux::Char(htslib::bam_aux2A(aux) as u8)),
                b'Z'|b'H' => {
                    let f = (aux.offset(1) as *const i8);
                    let x = c_str_to_bytes(&f);
                    Ok(Aux::String(copy_lifetime(self, x)))
                },
                _ => Err("unexpected aux type"),
            }
        }
    }
}
<commit_msg>added remaining record fields<commit_after>use htslib;
use std::ffi::c_str_to_bytes;
use std::slice::from_raw_parts;
use std::ffi::CString;
use std::mem::copy_lifetime;

#[derive(Debug)]
pub enum Aux<'a> {
    Integer(i32),
    String(&'a [u8]),
    Float(f64),
    Char(u8),
}

impl<'a> Aux<'a> {
    pub fn string(&self) -> &'a [u8] {
        match *self {
            Aux::String(x) => x,
            _ => panic!("not a string"),
        }
    }

    pub fn float(&self) -> f64 {
        match *self {
            Aux::Float(x) => x,
            _ => panic!("not a float"),
        }
    }

    pub fn integer(&self) -> i32 {
        match *self {
            Aux::Integer(x) => x,
            _ => panic!("not an integer"),
        }
    }

    pub fn char(&self) -> u8 {
        match *self {
            Aux::Char(x) => x,
            _ => panic!("not a character"),
        }
    }
}

pub struct Record<'a> {
    b: &'a htslib::bam1_t,
    data: &'a [u8],
}

impl<'a> Record<'a>{
    pub fn new(b: &'a htslib::bam1_t) -> Record {
        Record { b: b, data: unsafe { from_raw_parts((*b).data, b.l_data as usize) } }
    }

    fn tid(&self) -> i32 {
        self.b.core.tid
    }

    fn pos(&self) -> i32 {
        self.b.core.pos
    }

    fn bin(&self) -> u16 {
        self.b.core.bin
    }

    fn map_qual(&self) -> u8 {
        self.b.core.qual
    }

    fn flag(&self) -> u16 {
        self.b.core.flag
    }

    fn mtid(&self) -> i32 {
        self.b.core.mtid
    }

    fn mpos(&self) -> i32 {
        self.b.core.mpos
    }

    fn isize(&self) -> i32 {
        self.b.core.isize
    }

    fn qname_len(&self) -> usize {
        self.b.core.l_qname as usize
    }

    pub fn qname(&self) -> &[u8] {
        self.data[0..self.qname_len()].as_slice()
    }

    fn cigar_len(&self) -> usize {
        self.b.core.n_cigar as usize
    }

    pub fn bam_get_cigar(&self) -> &[u32] {
        let x = self.b.core.l_qname as usize;
        unsafe { from_raw_parts(self.data[self.qname_len()..].as_ptr() as *const u32, self.cigar_len()) }
    }

    fn seq_len(&self) -> usize {
        self.b.core.l_qseq as usize
    }

    pub fn seq(&self) -> &[u8] {
        self.data[self.qname_len() + self.cigar_len()*4..][..self.seq_len()].as_slice()
    }

    pub fn qual(&self) -> &[u8] {
        self.data[self.qname_len() + self.cigar_len()*4 + (self.seq_len()+1)/2..][..self.seq_len()].as_slice()
    }

    pub fn aux(&self, name: &[u8]) -> Result<Aux, &str> {
        let aux = unsafe { htslib::bam_aux_get(self.b, name.as_ptr() as *mut i8 ) };
        unsafe { println!("{}", *aux); }

        unsafe {
            match *aux {
                b'c'|b'C'|b's'|b'S'|b'i'|b'I' => Ok(Aux::Integer(htslib::bam_aux2i(aux))),
                b'f'|b'd' => Ok(Aux::Float(htslib::bam_aux2f(aux))),
                b'A' => Ok(Aux::Char(htslib::bam_aux2A(aux) as u8)),
                b'Z'|b'H' => {
                    let f = (aux.offset(1) as *const i8);
                    let x = c_str_to_bytes(&f);
                    Ok(Aux::String(copy_lifetime(self, x)))
                },
                _ => Err("unexpected aux type"),
            }
        }
    }
}
<|endoftext|>


<commit_before>//! History API

use std::collections::vec_deque;
use std::collections::VecDeque;
use std::fs::File;
use std::iter::DoubleEndedIterator;
use std::ops::Index;
use std::path::Path;

use super::Result;
use crate::config::{Config, HistoryDuplicates};

/// Search direction
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
    /// Search history forward
    Forward,
    /// Search history backward
    Reverse,
}

/// Current state of the history.
#[derive(Default)]
pub struct History {
    entries: VecDeque<String>,
    max_len: usize,
    pub(crate) ignore_space: bool,
    pub(crate) ignore_dups: bool,
}

impl History {
    // New multiline-aware history files start with `#V2\n` and have newlines
    // and backslashes escaped in them.
    const FILE_VERSION_V2: &'static str = "#V2";

    /// Default constructor
    pub fn new() -> Self {
        Self::with_config(Config::default())
    }

    /// Customized constructor with:
    /// - `Config::max_history_size()`,
    /// - `Config::history_ignore_space()`,
    /// - `Config::history_duplicates()`.
    pub fn with_config(config: Config) -> Self {
        Self {
            entries: VecDeque::new(),
            max_len: config.max_history_size(),
            ignore_space: config.history_ignore_space(),
            ignore_dups: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
        }
    }

    /// Return the history entry at position `index`, starting from 0.
    pub fn get(&self, index: usize) -> Option<&String> {
        self.entries.get(index)
    }

    /// Return the last history entry (i.e. previous command)
    pub fn last(&self) -> Option<&String> {
        self.entries.back()
    }

    /// Add a new entry in the history.
    pub fn add<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
        if self.max_len == 0 {
            return false;
        }
        if line.as_ref().is_empty()
            || (self.ignore_space
                && line
                    .as_ref()
                    .chars()
                    .next()
                    .map_or(true, char::is_whitespace))
        {
            return false;
        }
        if self.ignore_dups {
            if let Some(s) = self.entries.back() {
                if s == line.as_ref() {
                    return false;
                }
            }
        }
        if self.entries.len() == self.max_len {
            self.entries.pop_front();
        }
        self.entries.push_back(line.into());
        true
    }

    /// Return the number of entries in the history.
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Return true if the history has no entry.
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Set the maximum length for the history. This function can be called even
    /// if there is already some history, the function will make sure to retain
    /// just the latest `len` elements if the new history length value is
    /// smaller than the amount of items already inside the history.
    ///
    /// Like [stifle_history](http://cnswww.cns.cwru.
    /// edu/php/chet/readline/history.html#IDX11).
    pub fn set_max_len(&mut self, len: usize) {
        self.max_len = len;
        if len == 0 {
            self.entries.clear();
            return;
        }
        loop {
            if self.entries.len() <= len {
                break;
            }
            self.entries.pop_front();
        }
    }

    /// Save the history in the specified file.
    // TODO append_history
    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
    // TODO history_truncate_file
    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31
    pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
        use std::io::{BufWriter, Write};

        if self.is_empty() {
            return Ok(());
        }
        let old_umask = umask();
        let f = File::create(path);
        restore_umask(old_umask);
        let file = f?;
        fix_perm(&file);
        let mut wtr = BufWriter::new(file);
        wtr.write_all(Self::FILE_VERSION_V2.as_bytes())?;
        for entry in &self.entries {
            wtr.write_all(b"\n")?;
            let mut bytes = entry.as_bytes();
            while let Some(i) = memchr::memchr2(b'\\', b'\n', bytes) {
                wtr.write_all(&bytes[..i])?;
                if bytes[i] == b'\n' {
                    wtr.write_all(b"\\n")?;
                } else {
                    debug_assert_eq!(bytes[i], b'\\');
                    wtr.write_all(b"\\\\")?;
                }
                bytes = &bytes[i + 1..];
            }
            wtr.write_all(bytes)?; // remaining bytes
        }
        wtr.write_all(b"\n")?;
        // https://github.com/rust-lang/rust/issues/32677#issuecomment-204833485
        wtr.flush()?;
        Ok(())
    }

    /// Load the history from the specified file.
    ///
    /// # Errors
    /// Will return `Err` if path does not already exist or could not be read.
    pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
        use std::io::{BufRead, BufReader};

        let file = File::open(&path)?;
        let rdr = BufReader::new(file);
        let mut lines = rdr.lines();
        let mut v2 = false;
        if let Some(first) = lines.next() {
            let line = first?;
            if line == Self::FILE_VERSION_V2 {
                v2 = true;
            } else {
                self.add(line);
            }
        }
        for line in lines {
            let line = if v2 {
                line?.replace("\\n", "\n").replace("\\\\", "\\")
            } else {
                line?
            };
            if line.is_empty() {
                continue;
            }
            self.add(line); // TODO truncate to MAX_LINE
        }
        Ok(())
    }

    /// Clear history
    pub fn clear(&mut self) {
        self.entries.clear()
    }

    /// Search history (start position inclusive [0, len-1]).
    ///
    /// Return the absolute index of the nearest history entry that matches
    /// `term`.
    ///
    /// Return None if no entry contains `term` between [start, len -1] for
    /// forward search
    /// or between [0, start] for reverse search.
    pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
        let test = |entry: &String| entry.contains(term);
        self.search_match(term, start, dir, test)
    }

    /// Anchored search
    pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
        let test = |entry: &String| entry.starts_with(term);
        self.search_match(term, start, dir, test)
    }

    fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
    where
        F: Fn(&String) -> bool,
    {
        if term.is_empty() || start >= self.len() {
            return None;
        }
        match dir {
            Direction::Reverse => {
                let index = self
                    .entries
                    .iter()
                    .rev()
                    .skip(self.entries.len() - 1 - start)
                    .position(test);
                index.map(|index| start - index)
            }
            Direction::Forward => {
                let index = self.entries.iter().skip(start).position(test);
                index.map(|index| index + start)
            }
        }
    }

    /// Return a forward iterator.
    pub fn iter(&self) -> Iter<'_> {
        Iter(self.entries.iter())
    }
}

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

    fn index(&self, index: usize) -> &String {
        &self.entries[index]
    }
}

impl<'a> IntoIterator for &'a History {
    type IntoIter = Iter<'a>;
    type Item = &'a String;

    fn into_iter(self) -> Iter<'a> {
        self.iter()
    }
}

/// History iterator.
pub struct Iter<'a>(vec_deque::Iter<'a, String>);

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

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

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

impl<'a> DoubleEndedIterator for Iter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        self.0.next_back()
    }
}

cfg_if::cfg_if! {
    if #[cfg(any(windows, target_arch = "wasm32"))] {
        fn umask() -> u16 {
            0
        }

        fn restore_umask(_: u16) {}

        fn fix_perm(_: &File) {}
    } else if #[cfg(unix)] {
        fn umask() -> libc::mode_t {
            unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) }
        }

        fn restore_umask(old_umask: libc::mode_t) {
            unsafe {
                libc::umask(old_umask);
            }
        }

        fn fix_perm(file: &File) {
            use std::os::unix::io::AsRawFd;
            unsafe {
                libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Direction, History};
    use crate::config::Config;
    use crate::Result;

    fn init() -> History {
        let mut history = History::new();
        assert!(history.add("line1"));
        assert!(history.add("line2"));
        assert!(history.add("line3"));
        history
    }

    #[test]
    fn new() {
        let history = History::new();
        assert_eq!(0, history.entries.len());
    }

    #[test]
    fn add() {
        let config = Config::builder().history_ignore_space(true).build();
        let mut history = History::with_config(config);
        assert_eq!(config.max_history_size(), history.max_len);
        assert!(history.add("line1"));
        assert!(history.add("line2"));
        assert!(!history.add("line2"));
        assert!(!history.add(""));
        assert!(!history.add(" line3"));
    }

    #[test]
    fn set_max_len() {
        let mut history = init();
        history.set_max_len(1);
        assert_eq!(1, history.entries.len());
        assert_eq!(Some(&"line3".to_owned()), history.last());
    }

    #[test]
    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn save() -> Result<()> {
        check_save("line\nfour \\ abc")
    }

    #[test]
    fn save_windows_path() -> Result<()> {
        let path = "cd source\\repos\\forks\\nushell\\";
        check_save(path)
    }

    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn check_save(line: &str) -> Result<()> {
        let mut history = init();
        assert!(history.add(line));
        let tf = tempfile::NamedTempFile::new()?;

        history.save(tf.path())?;
        let mut history2 = History::new();
        history2.load(tf.path())?;
        for (a, b) in history.entries.iter().zip(history2.entries.iter()) {
            assert_eq!(a, b);
        }
        tf.close()?;
        Ok(())
    }

    #[test]
    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn load_legacy() -> Result<()> {
        use std::io::Write;
        let tf = tempfile::NamedTempFile::new()?;
        {
            let mut legacy = std::fs::File::create(tf.path())?;
            // Some data we'd accidentally corrupt if we got the version wrong
            let data = b"\
                test\\n \\abc \\123\n\
                123\\n\\\\n\n\
                abcde
            ";
            legacy.write_all(data)?;
            legacy.flush()?;
        }
        let mut history = History::new();
        history.load(tf.path())?;
        assert_eq!(history.entries[0], "test\\n \\abc \\123");
        assert_eq!(history.entries[1], "123\\n\\\\n");
        assert_eq!(history.entries[2], "abcde");

        tf.close()?;
        Ok(())
    }

    #[test]
    fn search() {
        let history = init();
        assert_eq!(None, history.search("", 0, Direction::Forward));
        assert_eq!(None, history.search("none", 0, Direction::Forward));
        assert_eq!(None, history.search("line", 3, Direction::Forward));

        assert_eq!(Some(0), history.search("line", 0, Direction::Forward));
        assert_eq!(Some(1), history.search("line", 1, Direction::Forward));
        assert_eq!(Some(2), history.search("line3", 1, Direction::Forward));
    }

    #[test]
    fn reverse_search() {
        let history = init();
        assert_eq!(None, history.search("", 2, Direction::Reverse));
        assert_eq!(None, history.search("none", 2, Direction::Reverse));
        assert_eq!(None, history.search("line", 3, Direction::Reverse));

        assert_eq!(Some(2), history.search("line", 2, Direction::Reverse));
        assert_eq!(Some(1), history.search("line", 1, Direction::Reverse));
        assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse));
    }
}
<commit_msg>Fix History::load<commit_after>//! History API

use log::warn;
use std::collections::vec_deque;
use std::collections::VecDeque;
use std::fs::File;
use std::iter::DoubleEndedIterator;
use std::ops::Index;
use std::path::Path;

use super::Result;
use crate::config::{Config, HistoryDuplicates};

/// Search direction
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Direction {
    /// Search history forward
    Forward,
    /// Search history backward
    Reverse,
}

/// Current state of the history.
#[derive(Default)]
pub struct History {
    entries: VecDeque<String>,
    max_len: usize,
    pub(crate) ignore_space: bool,
    pub(crate) ignore_dups: bool,
}

impl History {
    // New multiline-aware history files start with `#V2\n` and have newlines
    // and backslashes escaped in them.
    const FILE_VERSION_V2: &'static str = "#V2";

    /// Default constructor
    pub fn new() -> Self {
        Self::with_config(Config::default())
    }

    /// Customized constructor with:
    /// - `Config::max_history_size()`,
    /// - `Config::history_ignore_space()`,
    /// - `Config::history_duplicates()`.
    pub fn with_config(config: Config) -> Self {
        Self {
            entries: VecDeque::new(),
            max_len: config.max_history_size(),
            ignore_space: config.history_ignore_space(),
            ignore_dups: config.history_duplicates() == HistoryDuplicates::IgnoreConsecutive,
        }
    }

    /// Return the history entry at position `index`, starting from 0.
    pub fn get(&self, index: usize) -> Option<&String> {
        self.entries.get(index)
    }

    /// Return the last history entry (i.e. previous command)
    pub fn last(&self) -> Option<&String> {
        self.entries.back()
    }

    /// Add a new entry in the history.
    pub fn add<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
        if self.max_len == 0 {
            return false;
        }
        if line.as_ref().is_empty()
            || (self.ignore_space
                && line
                    .as_ref()
                    .chars()
                    .next()
                    .map_or(true, char::is_whitespace))
        {
            return false;
        }
        if self.ignore_dups {
            if let Some(s) = self.entries.back() {
                if s == line.as_ref() {
                    return false;
                }
            }
        }
        if self.entries.len() == self.max_len {
            self.entries.pop_front();
        }
        self.entries.push_back(line.into());
        true
    }

    /// Return the number of entries in the history.
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// Return true if the history has no entry.
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// Set the maximum length for the history. This function can be called even
    /// if there is already some history, the function will make sure to retain
    /// just the latest `len` elements if the new history length value is
    /// smaller than the amount of items already inside the history.
    ///
    /// Like [stifle_history](http://cnswww.cns.cwru.
    /// edu/php/chet/readline/history.html#IDX11).
    pub fn set_max_len(&mut self, len: usize) {
        self.max_len = len;
        if len == 0 {
            self.entries.clear();
            return;
        }
        loop {
            if self.entries.len() <= len {
                break;
            }
            self.entries.pop_front();
        }
    }

    /// Save the history in the specified file.
    // TODO append_history
    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX30
    // TODO history_truncate_file
    // http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX31
    pub fn save<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
        use std::io::{BufWriter, Write};

        if self.is_empty() {
            return Ok(());
        }
        let old_umask = umask();
        let f = File::create(path);
        restore_umask(old_umask);
        let file = f?;
        fix_perm(&file);
        let mut wtr = BufWriter::new(file);
        wtr.write_all(Self::FILE_VERSION_V2.as_bytes())?;
        for entry in &self.entries {
            wtr.write_all(b"\n")?;
            let mut bytes = entry.as_bytes();
            while let Some(i) = memchr::memchr2(b'\\', b'\n', bytes) {
                wtr.write_all(&bytes[..i])?;
                if bytes[i] == b'\n' {
                    wtr.write_all(b"\\n")?;
                } else {
                    debug_assert_eq!(bytes[i], b'\\');
                    wtr.write_all(b"\\\\")?;
                }
                bytes = &bytes[i + 1..];
            }
            wtr.write_all(bytes)?; // remaining bytes
        }
        wtr.write_all(b"\n")?;
        // https://github.com/rust-lang/rust/issues/32677#issuecomment-204833485
        wtr.flush()?;
        Ok(())
    }

    /// Load the history from the specified file.
    ///
    /// # Errors
    /// Will return `Err` if path does not already exist or could not be read.
    pub fn load<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
        use std::io::{BufRead, BufReader};

        let file = File::open(&path)?;
        let rdr = BufReader::new(file);
        let mut lines = rdr.lines();
        let mut v2 = false;
        if let Some(first) = lines.next() {
            let line = first?;
            if line == Self::FILE_VERSION_V2 {
                v2 = true;
            } else {
                self.add(line);
            }
        }
        for line in lines {
            let mut line = line?;
            if line.is_empty() {
                continue;
            }
            if v2 {
                let mut copy = None;
                let bytes = line.as_bytes();
                let mut i = 0;
                while let Some(j) = memchr::memchr(b'\\', &bytes[i..]) {
                    if copy.is_none() {
                        copy = Some(String::with_capacity(line.len()));
                    }
                    let s = copy.as_mut().unwrap();
                    s.push_str(&line[i..i + j]);
                    let k = i + j + 1; // escaped char idx
                    let bad = if k >= bytes.len() {
                        true
                    } else if bytes[k] == b'n' {
                        s.push('\n');
                        false
                    } else if bytes[k] == b'\\' {
                        s.push('\\');
                        false
                    } else {
                        true
                    };
                    if bad {
                        warn!(target: "rustyline", "bad escaped line: {}", line);
                        copy = None;
                        break;
                    }
                    i = k + 1;
                }
                if let Some(mut s) = copy {
                    s.push_str(&line[i..]);
                    line = s;
                }
            }
            self.add(line); // TODO truncate to MAX_LINE
        }
        Ok(())
    }

    /// Clear history
    pub fn clear(&mut self) {
        self.entries.clear()
    }

    /// Search history (start position inclusive [0, len-1]).
    ///
    /// Return the absolute index of the nearest history entry that matches
    /// `term`.
    ///
    /// Return None if no entry contains `term` between [start, len -1] for
    /// forward search
    /// or between [0, start] for reverse search.
    pub fn search(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
        let test = |entry: &String| entry.contains(term);
        self.search_match(term, start, dir, test)
    }

    /// Anchored search
    pub fn starts_with(&self, term: &str, start: usize, dir: Direction) -> Option<usize> {
        let test = |entry: &String| entry.starts_with(term);
        self.search_match(term, start, dir, test)
    }

    fn search_match<F>(&self, term: &str, start: usize, dir: Direction, test: F) -> Option<usize>
    where
        F: Fn(&String) -> bool,
    {
        if term.is_empty() || start >= self.len() {
            return None;
        }
        match dir {
            Direction::Reverse => {
                let index = self
                    .entries
                    .iter()
                    .rev()
                    .skip(self.entries.len() - 1 - start)
                    .position(test);
                index.map(|index| start - index)
            }
            Direction::Forward => {
                let index = self.entries.iter().skip(start).position(test);
                index.map(|index| index + start)
            }
        }
    }

    /// Return a forward iterator.
    pub fn iter(&self) -> Iter<'_> {
        Iter(self.entries.iter())
    }
}

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

    fn index(&self, index: usize) -> &String {
        &self.entries[index]
    }
}

impl<'a> IntoIterator for &'a History {
    type IntoIter = Iter<'a>;
    type Item = &'a String;

    fn into_iter(self) -> Iter<'a> {
        self.iter()
    }
}

/// History iterator.
pub struct Iter<'a>(vec_deque::Iter<'a, String>);

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

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

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.0.size_hint()
    }
}

impl<'a> DoubleEndedIterator for Iter<'a> {
    fn next_back(&mut self) -> Option<&'a String> {
        self.0.next_back()
    }
}

cfg_if::cfg_if! {
    if #[cfg(any(windows, target_arch = "wasm32"))] {
        fn umask() -> u16 {
            0
        }

        fn restore_umask(_: u16) {}

        fn fix_perm(_: &File) {}
    } else if #[cfg(unix)] {
        fn umask() -> libc::mode_t {
            unsafe { libc::umask(libc::S_IXUSR | libc::S_IRWXG | libc::S_IRWXO) }
        }

        fn restore_umask(old_umask: libc::mode_t) {
            unsafe {
                libc::umask(old_umask);
            }
        }

        fn fix_perm(file: &File) {
            use std::os::unix::io::AsRawFd;
            unsafe {
                libc::fchmod(file.as_raw_fd(), libc::S_IRUSR | libc::S_IWUSR);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Direction, History};
    use crate::config::Config;
    use crate::Result;

    fn init() -> History {
        let mut history = History::new();
        assert!(history.add("line1"));
        assert!(history.add("line2"));
        assert!(history.add("line3"));
        history
    }

    #[test]
    fn new() {
        let history = History::new();
        assert_eq!(0, history.entries.len());
    }

    #[test]
    fn add() {
        let config = Config::builder().history_ignore_space(true).build();
        let mut history = History::with_config(config);
        assert_eq!(config.max_history_size(), history.max_len);
        assert!(history.add("line1"));
        assert!(history.add("line2"));
        assert!(!history.add("line2"));
        assert!(!history.add(""));
        assert!(!history.add(" line3"));
    }

    #[test]
    fn set_max_len() {
        let mut history = init();
        history.set_max_len(1);
        assert_eq!(1, history.entries.len());
        assert_eq!(Some(&"line3".to_owned()), history.last());
    }

    #[test]
    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn save() -> Result<()> {
        check_save("line\nfour \\ abc")
    }

    #[test]
    fn save_windows_path() -> Result<()> {
        let path = "cd source\\repos\\forks\\nushell\\";
        check_save(path)
    }

    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn check_save(line: &str) -> Result<()> {
        let mut history = init();
        assert!(history.add(line));
        let tf = tempfile::NamedTempFile::new()?;

        history.save(tf.path())?;
        let mut history2 = History::new();
        history2.load(tf.path())?;
        for (a, b) in history.entries.iter().zip(history2.entries.iter()) {
            assert_eq!(a, b);
        }
        tf.close()?;
        Ok(())
    }

    #[test]
    #[cfg_attr(miri, ignore)] // unsupported operation: `getcwd` not available when isolation is enabled
    fn load_legacy() -> Result<()> {
        use std::io::Write;
        let tf = tempfile::NamedTempFile::new()?;
        {
            let mut legacy = std::fs::File::create(tf.path())?;
            // Some data we'd accidentally corrupt if we got the version wrong
            let data = b"\
                test\\n \\abc \\123\n\
                123\\n\\\\n\n\
                abcde
            ";
            legacy.write_all(data)?;
            legacy.flush()?;
        }
        let mut history = History::new();
        history.load(tf.path())?;
        assert_eq!(history.entries[0], "test\\n \\abc \\123");
        assert_eq!(history.entries[1], "123\\n\\\\n");
        assert_eq!(history.entries[2], "abcde");

        tf.close()?;
        Ok(())
    }

    #[test]
    fn search() {
        let history = init();
        assert_eq!(None, history.search("", 0, Direction::Forward));
        assert_eq!(None, history.search("none", 0, Direction::Forward));
        assert_eq!(None, history.search("line", 3, Direction::Forward));

        assert_eq!(Some(0), history.search("line", 0, Direction::Forward));
        assert_eq!(Some(1), history.search("line", 1, Direction::Forward));
        assert_eq!(Some(2), history.search("line3", 1, Direction::Forward));
    }

    #[test]
    fn reverse_search() {
        let history = init();
        assert_eq!(None, history.search("", 2, Direction::Reverse));
        assert_eq!(None, history.search("none", 2, Direction::Reverse));
        assert_eq!(None, history.search("line", 3, Direction::Reverse));

        assert_eq!(Some(2), history.search("line", 2, Direction::Reverse));
        assert_eq!(Some(1), history.search("line", 1, Direction::Reverse));
        assert_eq!(Some(0), history.search("line1", 1, Direction::Reverse));
    }
}
<|endoftext|>


<commit_before><commit_msg>virtio: vhost-user: console: add type to reset stdin to canon mode<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2020 The http-serve developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE.txt or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT.txt or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for directory traversal from local filesystem.
//! Currently Unix-only.

use http::header::{self, HeaderMap, HeaderValue};
use memchr::memchr;
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::os::unix::{ffi::OsStrExt, io::FromRawFd};
use std::path::Path;
use std::sync::Arc;

/// A builder for a `FsDir`.
pub struct FsDirBuilder {
    auto_gzip: bool,
}

impl FsDirBuilder {
    /// Enables or disables automatic gzipping based on `Accept-Encoding` request headers.
    ///
    /// Default is `true`.
    pub fn auto_gzip(mut self, auto_gzip: bool) -> Self {
        self.auto_gzip = auto_gzip;
        self
    }

    /// Returns a `FsDir` for the given path.
    pub fn for_path<P: AsRef<Path>>(&self, path: P) -> Result<Arc<FsDir>, Error> {
        FsDir::open(path.as_ref(), self.auto_gzip)
    }
}

/// A base directory for local filesystem traversal.
pub struct FsDir {
    auto_gzip: bool,
    fd: std::os::unix::io::RawFd,
}

impl FsDir {
    pub fn builder() -> FsDirBuilder {
        FsDirBuilder { auto_gzip: true }
    }

    fn open(path: &Path, auto_gzip: bool) -> Result<Arc<Self>, Error> {
        let path = path.as_os_str().as_bytes();
        if memchr(0, path).is_some() {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                "path contains NUL byte",
            ));
        }
        if path.len() >= libc::PATH_MAX.try_into().unwrap() {
            return Err(Error::new(ErrorKind::InvalidInput, "path is too long"));
        }
        let mut buf = [0u8; libc::PATH_MAX as usize];
        unsafe { std::ptr::copy_nonoverlapping(path.as_ptr(), buf.as_mut_ptr(), path.len()) };
        let fd = unsafe {
            libc::open(
                buf.as_ptr() as *const libc::c_char,
                libc::O_DIRECTORY | libc::O_CLOEXEC,
                0,
            )
        };
        if fd < 0 {
            return Err(Error::last_os_error());
        }
        Ok(Arc::new(FsDir { auto_gzip, fd }))
    }

    /// Opens a path within this base directory.
    ///
    /// If using `auto_gzip` (the default) and `req_hdrs` indicate the client supports `gzip`, will
    /// look for a `.gz`-suffixed version of this path first and note that in the returned `Node`.
    /// `.gz`-suffixed directories are ignored.
    ///
    /// Validates that `path` has no `..` segments or interior NULs. Currently doesn't check for
    /// symlinks, however. That may eventually be configurable via the builder.
    pub async fn get(
        self: Arc<Self>,
        path: &str,
        req_hdrs: &HeaderMap,
    ) -> Result<Node, Error> {
        if let Err(e) = validate_path(path) {
            return Err(Error::new(ErrorKind::InvalidInput, e));
        }
        let mut buf = Vec::with_capacity(path.len() + b".gz\0".len());
        buf.extend_from_slice(path.as_bytes());
        let should_gzip = self.auto_gzip && super::should_gzip(req_hdrs);
        tokio::task::spawn_blocking(move || -> Result<Node, Error> {
            if should_gzip {
                let path_len = buf.len();
                buf.extend_from_slice(&b".gz\0"[..]);
                match self.open_file(
                    // This is safe because we've ensured in validate_path that there are no
                    // interior NULs, and we've just appended a NUL.
                    unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..]) },
                ) {
                    Ok(file) => {
                        let metadata = file.metadata()?;
                        if !metadata.is_dir() {
                            return Ok(Node {
                                file,
                                metadata,
                                auto_gzip: self.auto_gzip,
                                is_gzipped: true,
                            });
                        }
                    }
                    Err(ref e) if e.kind() == ErrorKind::NotFound => {}
                    Err(e) => return Err(e),
                };
                buf.truncate(path_len);
            }

            buf.push(b'\0');

            // As in the case above, we've ensured buf contains exactly one NUL, at the end.
            let p = unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..]) };
            let file = self.open_file(p)?;
            let metadata = file.metadata()?;
            Ok(Node {
                file,
                metadata,
                auto_gzip: self.auto_gzip,
                is_gzipped: false,
            })
        })
        .await
        .unwrap_or_else(|e: tokio::task::JoinError| Err(Error::new(ErrorKind::Other, e)))
    }

    /// Opens the given file with a path relative to this directory.
    /// Performs the blocking I/O directly from this thread.
    fn open_file(&self, path: &CStr) -> Result<File, Error> {
        let fd =
            unsafe { libc::openat(self.fd, path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) };
        if fd < 0 {
            return Err(Error::last_os_error());
        }
        Ok(unsafe { File::from_raw_fd(fd) })
    }
}

impl Drop for FsDir {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd) };
    }
}

/// An opened path (aka inode on Unix) as returned by `FsDir::open`.
///
/// This is not necessarily a plain file; it could also be a directory, for example.
///
/// The caller can inspect it as desired. If it is a directory, the caller might pass the result of
/// `into_file()` to `nix::dir::Dir::from`. If it is a plain file, the caller might create an
/// `http_serve::Entity` with `into_file_entity()`.
pub struct Node {
    file: std::fs::File,
    metadata: std::fs::Metadata,
    auto_gzip: bool,
    is_gzipped: bool,
}

impl Node {
    /// Converts this node to a `std::fs::File`.
    pub fn into_file(self) -> std::fs::File {
        self.file
    }

    /// Converts this node (which must represent a plain file) into a `ChunkedReadFile`.
    /// The caller is expected to supply all headers. The function `add_encoding_headers`
    /// may be useful.
    pub fn into_file_entity<D, E>(
        self,
        headers: HeaderMap,
    ) -> Result<crate::file::ChunkedReadFile<D, E>, Error>
    where
        D: 'static + Send + Sync + bytes::Buf + From<Vec<u8>> + From<&'static [u8]>,
        E: 'static
            + Send
            + Sync
            + Into<Box<dyn std::error::Error + Send + Sync>>
            + From<Box<dyn std::error::Error + Send + Sync>>,
    {
        crate::file::ChunkedReadFile::new_with_metadata(self.file, &self.metadata, headers)
    }

    /// Returns the (already fetched) metadata for this node.
    pub fn metadata(&self) -> &std::fs::Metadata {
        &self.metadata
    }

    /// Returns the encoding this file is assumed to have applied to the caller's request.
    /// E.g., if automatic gzip compression is enabled and `index.html.gz` was found when the
    /// caller requested `index.html`, this will return `Some("gzip")`. If the caller requests
    /// `index.html.gz`, this will return `None` because the gzip encoding is built in to the
    /// caller's request.
    pub fn encoding(&self) -> Option<&'static str> {
        if self.is_gzipped {
            Some("gzip")
        } else {
            None
        }
    }

    /// Returns true iff the content varies with the request's `Accept-Encoding` header value.
    pub fn encoding_varies(&self) -> bool {
        self.auto_gzip
    }

    /// Adds `Content-Encoding` and `Vary` headers for the encoding to `hdrs`.
    ///
    /// Note if there are other `Vary` header components known to the caller, this method is
    /// inappropriate.
    pub fn add_encoding_headers(&self, hdrs: &mut HeaderMap) {
        if let Some(e) = self.encoding() {
            hdrs.insert(header::CONTENT_ENCODING, HeaderValue::from_static(e));
        }
        if self.auto_gzip {
            hdrs.insert(header::VARY, HeaderValue::from_static("accept-encoding"));
        }
    }
}

/// Ensures path is safe: no NUL bytes, not absolute, no `..` segments.
fn validate_path(path: &str) -> Result<(), &'static str> {
    if memchr::memchr(0, path.as_bytes()).is_some() {
        return Err("path contains NUL byte");
    }
    if path.as_bytes().first() == Some(&b'/') {
        return Err("path is absolute");
    }
    let mut left = path.as_bytes();
    loop {
        let next = memchr::memchr(b'/', left);
        let seg = &left[0..next.unwrap_or(left.len())];
        if seg == b".." {
            return Err("path contains .. segment");
        }
        match next {
            None => break,
            Some(n) => left = &left[n + 1..],
        };
    }
    Ok(())
}

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

    #[tokio::test(flavor = "multi_thread")]
    async fn path_with_interior_nul() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
        let e = match fsdir.get("foo\0bar", &HeaderMap::new()).await {
            Ok(_) => panic!("should have failed"),
            Err(e) => e,
        };
        assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
        assert_eq!(e.to_string(), "path contains NUL byte");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn path_with_parent_dir_segment() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();

        for p in &["..", "../foo", "foo/../bar", "foo/.."] {
            let e = match Arc::clone(&fsdir).get(p, &HeaderMap::new()).await {
                Ok(_) => panic!("should have failed"),
                Err(e) => e,
            };
            assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
            assert_eq!(e.to_string(), "path contains .. segment");
        }
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn absolute_path() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
        let e = match fsdir.get("/etc/passwd", &HeaderMap::new()).await {
            Ok(_) => panic!("should have failed"),
            Err(e) => e,
        };
        assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
        assert_eq!(e.to_string(), "path is absolute");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn regular_file() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            let p = "foo.txt";
            let contents = b"1234";
            {
                use std::io::Write;
                let mut f = File::create(tmp.path().join(p)).unwrap();
                f.write_all(contents).unwrap();
            }
            let f = fsdir.get("foo.txt", &HeaderMap::new()).await.unwrap();
            assert_eq!(f.metadata.len(), contents.len() as u64);
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn missing_file() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            match fsdir.get("nonexistent.txt", &HeaderMap::new()).await {
                Ok(_) => panic!("nonexistent file found?!?"),
                Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
            };
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn symlink_allowed_in_last_path_component() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            ::std::os::unix::fs::symlink("/etc/passwd", tmp.path().join("foo.txt")).unwrap();
            fsdir.get("foo.txt", &HeaderMap::new()).await.unwrap();
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn symlink_allowed_in_earlier_path_component() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            ::std::os::unix::fs::symlink("/etc", tmp.path().join("etc")).unwrap();
            fsdir.get("etc/passwd", &HeaderMap::new()).await.unwrap();
        })
        .await
        .unwrap()
    }
}
<commit_msg>cargo fmt<commit_after>// Copyright (c) 2020 The http-serve developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE.txt or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT.txt or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for directory traversal from local filesystem.
//! Currently Unix-only.

use http::header::{self, HeaderMap, HeaderValue};
use memchr::memchr;
use std::convert::TryInto;
use std::ffi::CStr;
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::os::unix::{ffi::OsStrExt, io::FromRawFd};
use std::path::Path;
use std::sync::Arc;

/// A builder for a `FsDir`.
pub struct FsDirBuilder {
    auto_gzip: bool,
}

impl FsDirBuilder {
    /// Enables or disables automatic gzipping based on `Accept-Encoding` request headers.
    ///
    /// Default is `true`.
    pub fn auto_gzip(mut self, auto_gzip: bool) -> Self {
        self.auto_gzip = auto_gzip;
        self
    }

    /// Returns a `FsDir` for the given path.
    pub fn for_path<P: AsRef<Path>>(&self, path: P) -> Result<Arc<FsDir>, Error> {
        FsDir::open(path.as_ref(), self.auto_gzip)
    }
}

/// A base directory for local filesystem traversal.
pub struct FsDir {
    auto_gzip: bool,
    fd: std::os::unix::io::RawFd,
}

impl FsDir {
    pub fn builder() -> FsDirBuilder {
        FsDirBuilder { auto_gzip: true }
    }

    fn open(path: &Path, auto_gzip: bool) -> Result<Arc<Self>, Error> {
        let path = path.as_os_str().as_bytes();
        if memchr(0, path).is_some() {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                "path contains NUL byte",
            ));
        }
        if path.len() >= libc::PATH_MAX.try_into().unwrap() {
            return Err(Error::new(ErrorKind::InvalidInput, "path is too long"));
        }
        let mut buf = [0u8; libc::PATH_MAX as usize];
        unsafe { std::ptr::copy_nonoverlapping(path.as_ptr(), buf.as_mut_ptr(), path.len()) };
        let fd = unsafe {
            libc::open(
                buf.as_ptr() as *const libc::c_char,
                libc::O_DIRECTORY | libc::O_CLOEXEC,
                0,
            )
        };
        if fd < 0 {
            return Err(Error::last_os_error());
        }
        Ok(Arc::new(FsDir { auto_gzip, fd }))
    }

    /// Opens a path within this base directory.
    ///
    /// If using `auto_gzip` (the default) and `req_hdrs` indicate the client supports `gzip`, will
    /// look for a `.gz`-suffixed version of this path first and note that in the returned `Node`.
    /// `.gz`-suffixed directories are ignored.
    ///
    /// Validates that `path` has no `..` segments or interior NULs. Currently doesn't check for
    /// symlinks, however. That may eventually be configurable via the builder.
    pub async fn get(self: Arc<Self>, path: &str, req_hdrs: &HeaderMap) -> Result<Node, Error> {
        if let Err(e) = validate_path(path) {
            return Err(Error::new(ErrorKind::InvalidInput, e));
        }
        let mut buf = Vec::with_capacity(path.len() + b".gz\0".len());
        buf.extend_from_slice(path.as_bytes());
        let should_gzip = self.auto_gzip && super::should_gzip(req_hdrs);
        tokio::task::spawn_blocking(move || -> Result<Node, Error> {
            if should_gzip {
                let path_len = buf.len();
                buf.extend_from_slice(&b".gz\0"[..]);
                match self.open_file(
                    // This is safe because we've ensured in validate_path that there are no
                    // interior NULs, and we've just appended a NUL.
                    unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..]) },
                ) {
                    Ok(file) => {
                        let metadata = file.metadata()?;
                        if !metadata.is_dir() {
                            return Ok(Node {
                                file,
                                metadata,
                                auto_gzip: self.auto_gzip,
                                is_gzipped: true,
                            });
                        }
                    }
                    Err(ref e) if e.kind() == ErrorKind::NotFound => {}
                    Err(e) => return Err(e),
                };
                buf.truncate(path_len);
            }

            buf.push(b'\0');

            // As in the case above, we've ensured buf contains exactly one NUL, at the end.
            let p = unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..]) };
            let file = self.open_file(p)?;
            let metadata = file.metadata()?;
            Ok(Node {
                file,
                metadata,
                auto_gzip: self.auto_gzip,
                is_gzipped: false,
            })
        })
        .await
        .unwrap_or_else(|e: tokio::task::JoinError| Err(Error::new(ErrorKind::Other, e)))
    }

    /// Opens the given file with a path relative to this directory.
    /// Performs the blocking I/O directly from this thread.
    fn open_file(&self, path: &CStr) -> Result<File, Error> {
        let fd =
            unsafe { libc::openat(self.fd, path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) };
        if fd < 0 {
            return Err(Error::last_os_error());
        }
        Ok(unsafe { File::from_raw_fd(fd) })
    }
}

impl Drop for FsDir {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd) };
    }
}

/// An opened path (aka inode on Unix) as returned by `FsDir::open`.
///
/// This is not necessarily a plain file; it could also be a directory, for example.
///
/// The caller can inspect it as desired. If it is a directory, the caller might pass the result of
/// `into_file()` to `nix::dir::Dir::from`. If it is a plain file, the caller might create an
/// `http_serve::Entity` with `into_file_entity()`.
pub struct Node {
    file: std::fs::File,
    metadata: std::fs::Metadata,
    auto_gzip: bool,
    is_gzipped: bool,
}

impl Node {
    /// Converts this node to a `std::fs::File`.
    pub fn into_file(self) -> std::fs::File {
        self.file
    }

    /// Converts this node (which must represent a plain file) into a `ChunkedReadFile`.
    /// The caller is expected to supply all headers. The function `add_encoding_headers`
    /// may be useful.
    pub fn into_file_entity<D, E>(
        self,
        headers: HeaderMap,
    ) -> Result<crate::file::ChunkedReadFile<D, E>, Error>
    where
        D: 'static + Send + Sync + bytes::Buf + From<Vec<u8>> + From<&'static [u8]>,
        E: 'static
            + Send
            + Sync
            + Into<Box<dyn std::error::Error + Send + Sync>>
            + From<Box<dyn std::error::Error + Send + Sync>>,
    {
        crate::file::ChunkedReadFile::new_with_metadata(self.file, &self.metadata, headers)
    }

    /// Returns the (already fetched) metadata for this node.
    pub fn metadata(&self) -> &std::fs::Metadata {
        &self.metadata
    }

    /// Returns the encoding this file is assumed to have applied to the caller's request.
    /// E.g., if automatic gzip compression is enabled and `index.html.gz` was found when the
    /// caller requested `index.html`, this will return `Some("gzip")`. If the caller requests
    /// `index.html.gz`, this will return `None` because the gzip encoding is built in to the
    /// caller's request.
    pub fn encoding(&self) -> Option<&'static str> {
        if self.is_gzipped {
            Some("gzip")
        } else {
            None
        }
    }

    /// Returns true iff the content varies with the request's `Accept-Encoding` header value.
    pub fn encoding_varies(&self) -> bool {
        self.auto_gzip
    }

    /// Adds `Content-Encoding` and `Vary` headers for the encoding to `hdrs`.
    ///
    /// Note if there are other `Vary` header components known to the caller, this method is
    /// inappropriate.
    pub fn add_encoding_headers(&self, hdrs: &mut HeaderMap) {
        if let Some(e) = self.encoding() {
            hdrs.insert(header::CONTENT_ENCODING, HeaderValue::from_static(e));
        }
        if self.auto_gzip {
            hdrs.insert(header::VARY, HeaderValue::from_static("accept-encoding"));
        }
    }
}

/// Ensures path is safe: no NUL bytes, not absolute, no `..` segments.
fn validate_path(path: &str) -> Result<(), &'static str> {
    if memchr::memchr(0, path.as_bytes()).is_some() {
        return Err("path contains NUL byte");
    }
    if path.as_bytes().first() == Some(&b'/') {
        return Err("path is absolute");
    }
    let mut left = path.as_bytes();
    loop {
        let next = memchr::memchr(b'/', left);
        let seg = &left[0..next.unwrap_or(left.len())];
        if seg == b".." {
            return Err("path contains .. segment");
        }
        match next {
            None => break,
            Some(n) => left = &left[n + 1..],
        };
    }
    Ok(())
}

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

    #[tokio::test(flavor = "multi_thread")]
    async fn path_with_interior_nul() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
        let e = match fsdir.get("foo\0bar", &HeaderMap::new()).await {
            Ok(_) => panic!("should have failed"),
            Err(e) => e,
        };
        assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
        assert_eq!(e.to_string(), "path contains NUL byte");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn path_with_parent_dir_segment() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();

        for p in &["..", "../foo", "foo/../bar", "foo/.."] {
            let e = match Arc::clone(&fsdir).get(p, &HeaderMap::new()).await {
                Ok(_) => panic!("should have failed"),
                Err(e) => e,
            };
            assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
            assert_eq!(e.to_string(), "path contains .. segment");
        }
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn absolute_path() {
        let tmp = tempfile::tempdir().unwrap();
        let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
        let e = match fsdir.get("/etc/passwd", &HeaderMap::new()).await {
            Ok(_) => panic!("should have failed"),
            Err(e) => e,
        };
        assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
        assert_eq!(e.to_string(), "path is absolute");
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn regular_file() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            let p = "foo.txt";
            let contents = b"1234";
            {
                use std::io::Write;
                let mut f = File::create(tmp.path().join(p)).unwrap();
                f.write_all(contents).unwrap();
            }
            let f = fsdir.get("foo.txt", &HeaderMap::new()).await.unwrap();
            assert_eq!(f.metadata.len(), contents.len() as u64);
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn missing_file() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            match fsdir.get("nonexistent.txt", &HeaderMap::new()).await {
                Ok(_) => panic!("nonexistent file found?!?"),
                Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
            };
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn symlink_allowed_in_last_path_component() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            ::std::os::unix::fs::symlink("/etc/passwd", tmp.path().join("foo.txt")).unwrap();
            fsdir.get("foo.txt", &HeaderMap::new()).await.unwrap();
        })
        .await
        .unwrap()
    }

    #[tokio::test(flavor = "multi_thread")]
    async fn symlink_allowed_in_earlier_path_component() {
        let tmp = tempfile::tempdir().unwrap();
        tokio::spawn(async move {
            let fsdir = FsDir::builder().for_path(tmp.path()).unwrap();
            ::std::os::unix::fs::symlink("/etc", tmp.path().join("etc")).unwrap();
            fsdir.get("etc/passwd", &HeaderMap::new()).await.unwrap();
        })
        .await
        .unwrap()
    }
}
<|endoftext|>