<commit_before>// This file is part of the uutils coreutils package.
//
// (c) mahkoh (ju.orth [at] gmail [dot] com)
// (c) Daniel Rocco <drocco@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp

mod parser;

use clap::{crate_version, App, AppSettings};
use parser::{parse, Op, Symbol, UnaryOp};
use std::ffi::{OsStr, OsString};
use std::path::Path;
use uucore::executable;

const USAGE: &str = "test EXPRESSION
or:  test
or:  [ EXPRESSION ]
or:  [ ]
or:  [ OPTION";

// We use after_help so that this comes after the usage string (it would come before if we used about)
const AFTER_HELP: &str = "
Exit with the status determined by EXPRESSION.

An omitted EXPRESSION defaults to false.  Otherwise,
EXPRESSION is true or false and sets exit status.  It is one of:

  ( EXPRESSION )               EXPRESSION is true
  ! EXPRESSION                 EXPRESSION is false
  EXPRESSION1 -a EXPRESSION2   both EXPRESSION1 and EXPRESSION2 are true
  EXPRESSION1 -o EXPRESSION2   either EXPRESSION1 or EXPRESSION2 is true

  -n STRING            the length of STRING is nonzero
  STRING               equivalent to -n STRING
  -z STRING            the length of STRING is zero
  STRING1 = STRING2    the strings are equal
  STRING1 != STRING2   the strings are not equal

  INTEGER1 -eq INTEGER2   INTEGER1 is equal to INTEGER2
  INTEGER1 -ge INTEGER2   INTEGER1 is greater than or equal to INTEGER2
  INTEGER1 -gt INTEGER2   INTEGER1 is greater than INTEGER2
  INTEGER1 -le INTEGER2   INTEGER1 is less than or equal to INTEGER2
  INTEGER1 -lt INTEGER2   INTEGER1 is less than INTEGER2
  INTEGER1 -ne INTEGER2   INTEGER1 is not equal to INTEGER2

  FILE1 -ef FILE2   FILE1 and FILE2 have the same device and inode numbers
  FILE1 -nt FILE2   FILE1 is newer (modification date) than FILE2
  FILE1 -ot FILE2   FILE1 is older than FILE2

  -b FILE     FILE exists and is block special
  -c FILE     FILE exists and is character special
  -d FILE     FILE exists and is a directory
  -e FILE     FILE exists
  -f FILE     FILE exists and is a regular file
  -g FILE     FILE exists and is set-group-ID
  -G FILE     FILE exists and is owned by the effective group ID
  -h FILE     FILE exists and is a symbolic link (same as -L)
  -k FILE     FILE exists and has its sticky bit set
  -L FILE     FILE exists and is a symbolic link (same as -h)
  -N FILE     FILE exists and has been modified since it was last read
  -O FILE     FILE exists and is owned by the effective user ID
  -p FILE     FILE exists and is a named pipe
  -r FILE     FILE exists and read permission is granted
  -s FILE     FILE exists and has a size greater than zero
  -S FILE     FILE exists and is a socket
  -t FD       file descriptor FD is opened on a terminal
  -u FILE     FILE exists and its set-user-ID bit is set
  -w FILE     FILE exists and write permission is granted
  -x FILE     FILE exists and execute (or search) permission is granted

Except for -h and -L, all FILE-related tests dereference symbolic links.
Beware that parentheses need to be escaped (e.g., by backslashes) for shells.
INTEGER may also be -l STRING, which evaluates to the length of STRING.

NOTE: Binary -a and -o are inherently ambiguous.  Use 'test EXPR1 && test
EXPR2' or 'test EXPR1 || test EXPR2' instead.

NOTE: [ honors the --help and --version options, but test does not.
test treats each of those as it treats any other nonempty STRING.

NOTE: your shell may have its own version of test and/or [, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.";

pub fn uu_app() -> App<'static, 'static> {
    App::new(executable!())
        .setting(AppSettings::DisableHelpFlags)
        .setting(AppSettings::DisableVersion)
}

pub fn uumain(mut args: impl uucore::Args) -> i32 {
    let program = args.next().unwrap_or_else(|| OsString::from("test"));
    let binary_name = Path::new(&program)
        .file_name()
        .unwrap_or_else(|| OsStr::new("test"))
        .to_string_lossy();
    let mut args: Vec<_> = args.collect();

    if binary_name.ends_with('[') {
        // If invoked as [ we should recognize --help and --version (but not -h or -v)
        if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") {
            // Let clap pretty-print help and version
            App::new(binary_name)
                .version(crate_version!())
                .usage(USAGE)
                .after_help(AFTER_HELP)
                // Disable printing of -h and -v as valid alternatives for --help and --version,
                // since we don't recognize -h and -v as help/version flags.
                .setting(AppSettings::NeedsLongHelp)
                .setting(AppSettings::NeedsLongVersion)
                .get_matches_from(std::iter::once(program).chain(args.into_iter()));
            return 0;
        }
        // If invoked via name '[', matching ']' must be in the last arg
        let last = args.pop();
        if last != Some(OsString::from("]")) {
            eprintln!("[: missing ']'");
            return 2;
        }
    }

    let result = parse(args).and_then(|mut stack| eval(&mut stack));

    match result {
        Ok(result) => {
            if result {
                0
            } else {
                1
            }
        }
        Err(e) => {
            eprintln!("test: {}", e);
            2
        }
    }
}

/// Evaluate a stack of Symbols, returning the result of the evaluation or
/// an error message if evaluation failed.
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
    macro_rules! pop_literal {
        () => {
            match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                _ => panic!(),
            }
        };
    }

    let s = stack.pop();

    match s {
        Some(Symbol::Bang) => {
            let result = eval(stack)?;

            Ok(!result)
        }
        Some(Symbol::Op(Op::StringOp(op))) => {
            let b = stack.pop();
            let a = stack.pop();
            Ok(if op == "!=" { a != b } else { a == b })
        }
        Some(Symbol::Op(Op::IntOp(op))) => {
            let b = pop_literal!();
            let a = pop_literal!();

            Ok(integers(&a, &b, &op)?)
        }
        Some(Symbol::Op(Op::FileOp(_op))) => unimplemented!(),
        Some(Symbol::UnaryOp(UnaryOp::StrlenOp(op))) => {
            let s = match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                Some(Symbol::None) => OsString::from(""),
                None => {
                    return Ok(true);
                }
                _ => {
                    return Err(format!("missing argument after '{:?}'", op));
                }
            };

            Ok(if op == "-z" {
                s.is_empty()
            } else {
                !s.is_empty()
            })
        }
        Some(Symbol::UnaryOp(UnaryOp::FiletestOp(op))) => {
            let op = op.to_string_lossy();

            let f = pop_literal!();

            Ok(match op.as_ref() {
                "-b" => path(&f, PathCondition::BlockSpecial),
                "-c" => path(&f, PathCondition::CharacterSpecial),
                "-d" => path(&f, PathCondition::Directory),
                "-e" => path(&f, PathCondition::Exists),
                "-f" => path(&f, PathCondition::Regular),
                "-g" => path(&f, PathCondition::GroupIdFlag),
                "-G" => path(&f, PathCondition::GroupOwns),
                "-h" => path(&f, PathCondition::SymLink),
                "-k" => path(&f, PathCondition::Sticky),
                "-L" => path(&f, PathCondition::SymLink),
                "-O" => path(&f, PathCondition::UserOwns),
                "-p" => path(&f, PathCondition::Fifo),
                "-r" => path(&f, PathCondition::Readable),
                "-S" => path(&f, PathCondition::Socket),
                "-s" => path(&f, PathCondition::NonEmpty),
                "-t" => isatty(&f)?,
                "-u" => path(&f, PathCondition::UserIdFlag),
                "-w" => path(&f, PathCondition::Writable),
                "-x" => path(&f, PathCondition::Executable),
                _ => panic!(),
            })
        }
        Some(Symbol::Literal(s)) => Ok(!s.is_empty()),
        Some(Symbol::None) => Ok(false),
        Some(Symbol::BoolOp(op)) => {
            let b = eval(stack)?;
            let a = eval(stack)?;

            Ok(if op == "-a" { a && b } else { a || b })
        }
        None => Ok(false),
        _ => Err("expected value".to_string()),
    }
}

fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
    let format_err = |value| format!("invalid integer '{}'", value);

    let a = a.to_string_lossy();
    let a: i64 = a.parse().map_err(|_| format_err(a))?;

    let b = b.to_string_lossy();
    let b: i64 = b.parse().map_err(|_| format_err(b))?;

    let operator = op.to_string_lossy();
    Ok(match operator.as_ref() {
        "-eq" => a == b,
        "-ne" => a != b,
        "-gt" => a > b,
        "-ge" => a >= b,
        "-lt" => a < b,
        "-le" => a <= b,
        _ => return Err(format!("unknown operator '{}'", operator)),
    })
}

fn isatty(fd: &OsStr) -> Result<bool, String> {
    let fd = fd.to_string_lossy();

    fd.parse()
        .map_err(|_| format!("invalid integer '{}'", fd))
        .map(|i| {
            #[cfg(not(target_os = "redox"))]
            unsafe {
                libc::isatty(i) == 1
            }
            #[cfg(target_os = "redox")]
            syscall::dup(i, b"termios").map(syscall::close).is_ok()
        })
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIdFlag,
    GroupOwns,
    SymLink,
    Sticky,
    UserOwns,
    Fifo,
    Readable,
    Socket,
    NonEmpty,
    UserIdFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::{self, Metadata};
    use std::os::unix::fs::{FileTypeExt, MetadataExt};

    const S_ISUID: u32 = 0o4000;
    const S_ISGID: u32 = 0o2000;
    const S_ISVTX: u32 = 0o1000;

    enum Permission {
        Read = 0o4,
        Write = 0o2,
        Execute = 0o1,
    }

    let geteuid = || {
        #[cfg(not(target_os = "redox"))]
        let euid = unsafe { libc::geteuid() };
        #[cfg(target_os = "redox")]
        let euid = syscall::geteuid().unwrap() as u32;

        euid
    };

    let getegid = || {
        #[cfg(not(target_os = "redox"))]
        let egid = unsafe { libc::getegid() };
        #[cfg(target_os = "redox")]
        let egid = syscall::getegid().unwrap() as u32;

        egid
    };

    let perm = |metadata: Metadata, p: Permission| {
        if geteuid() == metadata.uid() {
            metadata.mode() & ((p as u32) << 6) != 0
        } else if getegid() == metadata.gid() {
            metadata.mode() & ((p as u32) << 3) != 0
        } else {
            metadata.mode() & (p as u32) != 0
        }
    };

    let metadata = if condition == PathCondition::SymLink {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    let metadata = match metadata {
        Ok(metadata) => metadata,
        Err(_) => {
            return false;
        }
    };

    let file_type = metadata.file_type();

    match condition {
        PathCondition::BlockSpecial => file_type.is_block_device(),
        PathCondition::CharacterSpecial => file_type.is_char_device(),
        PathCondition::Directory => file_type.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => file_type.is_file(),
        PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
        PathCondition::GroupOwns => metadata.gid() == getegid(),
        PathCondition::SymLink => metadata.file_type().is_symlink(),
        PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
        PathCondition::UserOwns => metadata.uid() == geteuid(),
        PathCondition::Fifo => file_type.is_fifo(),
        PathCondition::Readable => perm(metadata, Permission::Read),
        PathCondition::Socket => file_type.is_socket(),
        PathCondition::NonEmpty => metadata.size() > 0,
        PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0,
        PathCondition::Writable => perm(metadata, Permission::Write),
        PathCondition::Executable => perm(metadata, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::metadata;

    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };

    match condition {
        PathCondition::BlockSpecial => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory => stat.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => stat.is_file(),
        PathCondition::GroupIdFlag => false,
        PathCondition::GroupOwns => unimplemented!(),
        PathCondition::SymLink => false,
        PathCondition::Sticky => false,
        PathCondition::UserOwns => unimplemented!(),
        PathCondition::Fifo => false,
        PathCondition::Readable => false, // TODO
        PathCondition::Socket => false,
        PathCondition::NonEmpty => stat.len() > 0,
        PathCondition::UserIdFlag => false,
        PathCondition::Writable => false,   // TODO
        PathCondition::Executable => false, // TODO
    }
}
<commit_msg>Fix clippy warnings in test.rs<commit_after>// This file is part of the uutils coreutils package.
//
// (c) mahkoh (ju.orth [at] gmail [dot] com)
// (c) Daniel Rocco <drocco@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp

mod parser;

use clap::{crate_version, App, AppSettings};
use parser::{parse, Operator, Symbol, UnaryOperator};
use std::ffi::{OsStr, OsString};
use std::path::Path;

const USAGE: &str = "test EXPRESSION
or:  test
or:  [ EXPRESSION ]
or:  [ ]
or:  [ OPTION";

// We use after_help so that this comes after the usage string (it would come before if we used about)
const AFTER_HELP: &str = "
Exit with the status determined by EXPRESSION.

An omitted EXPRESSION defaults to false.  Otherwise,
EXPRESSION is true or false and sets exit status.  It is one of:

  ( EXPRESSION )               EXPRESSION is true
  ! EXPRESSION                 EXPRESSION is false
  EXPRESSION1 -a EXPRESSION2   both EXPRESSION1 and EXPRESSION2 are true
  EXPRESSION1 -o EXPRESSION2   either EXPRESSION1 or EXPRESSION2 is true

  -n STRING            the length of STRING is nonzero
  STRING               equivalent to -n STRING
  -z STRING            the length of STRING is zero
  STRING1 = STRING2    the strings are equal
  STRING1 != STRING2   the strings are not equal

  INTEGER1 -eq INTEGER2   INTEGER1 is equal to INTEGER2
  INTEGER1 -ge INTEGER2   INTEGER1 is greater than or equal to INTEGER2
  INTEGER1 -gt INTEGER2   INTEGER1 is greater than INTEGER2
  INTEGER1 -le INTEGER2   INTEGER1 is less than or equal to INTEGER2
  INTEGER1 -lt INTEGER2   INTEGER1 is less than INTEGER2
  INTEGER1 -ne INTEGER2   INTEGER1 is not equal to INTEGER2

  FILE1 -ef FILE2   FILE1 and FILE2 have the same device and inode numbers
  FILE1 -nt FILE2   FILE1 is newer (modification date) than FILE2
  FILE1 -ot FILE2   FILE1 is older than FILE2

  -b FILE     FILE exists and is block special
  -c FILE     FILE exists and is character special
  -d FILE     FILE exists and is a directory
  -e FILE     FILE exists
  -f FILE     FILE exists and is a regular file
  -g FILE     FILE exists and is set-group-ID
  -G FILE     FILE exists and is owned by the effective group ID
  -h FILE     FILE exists and is a symbolic link (same as -L)
  -k FILE     FILE exists and has its sticky bit set
  -L FILE     FILE exists and is a symbolic link (same as -h)
  -N FILE     FILE exists and has been modified since it was last read
  -O FILE     FILE exists and is owned by the effective user ID
  -p FILE     FILE exists and is a named pipe
  -r FILE     FILE exists and read permission is granted
  -s FILE     FILE exists and has a size greater than zero
  -S FILE     FILE exists and is a socket
  -t FD       file descriptor FD is opened on a terminal
  -u FILE     FILE exists and its set-user-ID bit is set
  -w FILE     FILE exists and write permission is granted
  -x FILE     FILE exists and execute (or search) permission is granted

Except for -h and -L, all FILE-related tests dereference symbolic links.
Beware that parentheses need to be escaped (e.g., by backslashes) for shells.
INTEGER may also be -l STRING, which evaluates to the length of STRING.

NOTE: Binary -a and -o are inherently ambiguous.  Use 'test EXPR1 && test
EXPR2' or 'test EXPR1 || test EXPR2' instead.

NOTE: [ honors the --help and --version options, but test does not.
test treats each of those as it treats any other nonempty STRING.

NOTE: your shell may have its own version of test and/or [, which usually supersedes
the version described here.  Please refer to your shell's documentation
for details about the options it supports.";

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .setting(AppSettings::DisableHelpFlags)
        .setting(AppSettings::DisableVersion)
}

pub fn uumain(mut args: impl uucore::Args) -> i32 {
    let program = args.next().unwrap_or_else(|| OsString::from("test"));
    let binary_name = Path::new(&program)
        .file_name()
        .unwrap_or_else(|| OsStr::new("test"))
        .to_string_lossy();
    let mut args: Vec<_> = args.collect();

    if binary_name.ends_with('[') {
        // If invoked as [ we should recognize --help and --version (but not -h or -v)
        if args.len() == 1 && (args[0] == "--help" || args[0] == "--version") {
            // Let clap pretty-print help and version
            App::new(binary_name)
                .version(crate_version!())
                .usage(USAGE)
                .after_help(AFTER_HELP)
                // Disable printing of -h and -v as valid alternatives for --help and --version,
                // since we don't recognize -h and -v as help/version flags.
                .setting(AppSettings::NeedsLongHelp)
                .setting(AppSettings::NeedsLongVersion)
                .get_matches_from(std::iter::once(program).chain(args.into_iter()));
            return 0;
        }
        // If invoked via name '[', matching ']' must be in the last arg
        let last = args.pop();
        if last != Some(OsString::from("]")) {
            eprintln!("[: missing ']'");
            return 2;
        }
    }

    let result = parse(args).and_then(|mut stack| eval(&mut stack));

    match result {
        Ok(result) => {
            if result {
                0
            } else {
                1
            }
        }
        Err(e) => {
            eprintln!("test: {}", e);
            2
        }
    }
}

/// Evaluate a stack of Symbols, returning the result of the evaluation or
/// an error message if evaluation failed.
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
    macro_rules! pop_literal {
        () => {
            match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                _ => panic!(),
            }
        };
    }

    let s = stack.pop();

    match s {
        Some(Symbol::Bang) => {
            let result = eval(stack)?;

            Ok(!result)
        }
        Some(Symbol::Op(Operator::String(op))) => {
            let b = stack.pop();
            let a = stack.pop();
            Ok(if op == "!=" { a != b } else { a == b })
        }
        Some(Symbol::Op(Operator::Int(op))) => {
            let b = pop_literal!();
            let a = pop_literal!();

            Ok(integers(&a, &b, &op)?)
        }
        Some(Symbol::Op(Operator::File(_op))) => unimplemented!(),
        Some(Symbol::UnaryOp(UnaryOperator::StrlenOp(op))) => {
            let s = match stack.pop() {
                Some(Symbol::Literal(s)) => s,
                Some(Symbol::None) => OsString::from(""),
                None => {
                    return Ok(true);
                }
                _ => {
                    return Err(format!("missing argument after '{:?}'", op));
                }
            };

            Ok(if op == "-z" {
                s.is_empty()
            } else {
                !s.is_empty()
            })
        }
        Some(Symbol::UnaryOp(UnaryOperator::FiletestOp(op))) => {
            let op = op.to_string_lossy();

            let f = pop_literal!();

            Ok(match op.as_ref() {
                "-b" => path(&f, PathCondition::BlockSpecial),
                "-c" => path(&f, PathCondition::CharacterSpecial),
                "-d" => path(&f, PathCondition::Directory),
                "-e" => path(&f, PathCondition::Exists),
                "-f" => path(&f, PathCondition::Regular),
                "-g" => path(&f, PathCondition::GroupIdFlag),
                "-G" => path(&f, PathCondition::GroupOwns),
                "-h" => path(&f, PathCondition::SymLink),
                "-k" => path(&f, PathCondition::Sticky),
                "-L" => path(&f, PathCondition::SymLink),
                "-O" => path(&f, PathCondition::UserOwns),
                "-p" => path(&f, PathCondition::Fifo),
                "-r" => path(&f, PathCondition::Readable),
                "-S" => path(&f, PathCondition::Socket),
                "-s" => path(&f, PathCondition::NonEmpty),
                "-t" => isatty(&f)?,
                "-u" => path(&f, PathCondition::UserIdFlag),
                "-w" => path(&f, PathCondition::Writable),
                "-x" => path(&f, PathCondition::Executable),
                _ => panic!(),
            })
        }
        Some(Symbol::Literal(s)) => Ok(!s.is_empty()),
        Some(Symbol::None) => Ok(false),
        Some(Symbol::BoolOp(op)) => {
            let b = eval(stack)?;
            let a = eval(stack)?;

            Ok(if op == "-a" { a && b } else { a || b })
        }
        None => Ok(false),
        _ => Err("expected value".to_string()),
    }
}

fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
    let format_err = |value| format!("invalid integer '{}'", value);

    let a = a.to_string_lossy();
    let a: i64 = a.parse().map_err(|_| format_err(a))?;

    let b = b.to_string_lossy();
    let b: i64 = b.parse().map_err(|_| format_err(b))?;

    let operator = op.to_string_lossy();
    Ok(match operator.as_ref() {
        "-eq" => a == b,
        "-ne" => a != b,
        "-gt" => a > b,
        "-ge" => a >= b,
        "-lt" => a < b,
        "-le" => a <= b,
        _ => return Err(format!("unknown operator '{}'", operator)),
    })
}

fn isatty(fd: &OsStr) -> Result<bool, String> {
    let fd = fd.to_string_lossy();

    fd.parse()
        .map_err(|_| format!("invalid integer '{}'", fd))
        .map(|i| {
            #[cfg(not(target_os = "redox"))]
            unsafe {
                libc::isatty(i) == 1
            }
            #[cfg(target_os = "redox")]
            syscall::dup(i, b"termios").map(syscall::close).is_ok()
        })
}

#[derive(Eq, PartialEq)]
enum PathCondition {
    BlockSpecial,
    CharacterSpecial,
    Directory,
    Exists,
    Regular,
    GroupIdFlag,
    GroupOwns,
    SymLink,
    Sticky,
    UserOwns,
    Fifo,
    Readable,
    Socket,
    NonEmpty,
    UserIdFlag,
    Writable,
    Executable,
}

#[cfg(not(windows))]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::{self, Metadata};
    use std::os::unix::fs::{FileTypeExt, MetadataExt};

    const S_ISUID: u32 = 0o4000;
    const S_ISGID: u32 = 0o2000;
    const S_ISVTX: u32 = 0o1000;

    enum Permission {
        Read = 0o4,
        Write = 0o2,
        Execute = 0o1,
    }

    let geteuid = || {
        #[cfg(not(target_os = "redox"))]
        let euid = unsafe { libc::geteuid() };
        #[cfg(target_os = "redox")]
        let euid = syscall::geteuid().unwrap() as u32;

        euid
    };

    let getegid = || {
        #[cfg(not(target_os = "redox"))]
        let egid = unsafe { libc::getegid() };
        #[cfg(target_os = "redox")]
        let egid = syscall::getegid().unwrap() as u32;

        egid
    };

    let perm = |metadata: Metadata, p: Permission| {
        if geteuid() == metadata.uid() {
            metadata.mode() & ((p as u32) << 6) != 0
        } else if getegid() == metadata.gid() {
            metadata.mode() & ((p as u32) << 3) != 0
        } else {
            metadata.mode() & (p as u32) != 0
        }
    };

    let metadata = if condition == PathCondition::SymLink {
        fs::symlink_metadata(path)
    } else {
        fs::metadata(path)
    };

    let metadata = match metadata {
        Ok(metadata) => metadata,
        Err(_) => {
            return false;
        }
    };

    let file_type = metadata.file_type();

    match condition {
        PathCondition::BlockSpecial => file_type.is_block_device(),
        PathCondition::CharacterSpecial => file_type.is_char_device(),
        PathCondition::Directory => file_type.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => file_type.is_file(),
        PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
        PathCondition::GroupOwns => metadata.gid() == getegid(),
        PathCondition::SymLink => metadata.file_type().is_symlink(),
        PathCondition::Sticky => metadata.mode() & S_ISVTX != 0,
        PathCondition::UserOwns => metadata.uid() == geteuid(),
        PathCondition::Fifo => file_type.is_fifo(),
        PathCondition::Readable => perm(metadata, Permission::Read),
        PathCondition::Socket => file_type.is_socket(),
        PathCondition::NonEmpty => metadata.size() > 0,
        PathCondition::UserIdFlag => metadata.mode() & S_ISUID != 0,
        PathCondition::Writable => perm(metadata, Permission::Write),
        PathCondition::Executable => perm(metadata, Permission::Execute),
    }
}

#[cfg(windows)]
fn path(path: &OsStr, condition: PathCondition) -> bool {
    use std::fs::metadata;

    let stat = match metadata(path) {
        Ok(s) => s,
        _ => return false,
    };

    match condition {
        PathCondition::BlockSpecial => false,
        PathCondition::CharacterSpecial => false,
        PathCondition::Directory => stat.is_dir(),
        PathCondition::Exists => true,
        PathCondition::Regular => stat.is_file(),
        PathCondition::GroupIdFlag => false,
        PathCondition::GroupOwns => unimplemented!(),
        PathCondition::SymLink => false,
        PathCondition::Sticky => false,
        PathCondition::UserOwns => unimplemented!(),
        PathCondition::Fifo => false,
        PathCondition::Readable => false, // TODO
        PathCondition::Socket => false,
        PathCondition::NonEmpty => stat.len() > 0,
        PathCondition::UserIdFlag => false,
        PathCondition::Writable => false,   // TODO
        PathCondition::Executable => false, // TODO
    }
}
<|endoftext|>


<commit_before>use std::cmp::min;

use color;
use view::{View,IdView,SizeRequest,DimensionRequest};
use event::{Event,EventResult,Key};
use vec::Vec2;
use printer::Printer;
use super::scroll::ScrollBase;

struct Item<T> {
    label: String,
    value: T,
}

impl <T> Item<T> {
    fn new(label: &str, value: T) -> Self {
        Item {
            label: label.to_string(),
            value: value,
        }
    }
}

/// View to select an item among a list.
///
/// It contains a list of values of type T, with associated labels.
pub struct SelectView<T=String> {
    items: Vec<Item<T>>,
    focus: usize,
    scrollbase: ScrollBase,
}

impl <T> SelectView<T> {
    /// Creates a new empty SelectView.
    pub fn new() -> Self {
        SelectView {
            items: Vec::new(),
            focus: 0,
            scrollbase: ScrollBase::new(),
        }
    }

    /// Returns the value of the currently selected item. Panics if the list is empty.
    pub fn selection(&self) -> &T {
        &self.items[self.focus].value
    }

    /// Adds a item to the list, with given label and value.
    pub fn add_item(&mut self, label: &str, value: T) {
        self.items.push(Item::new(label,value));
    }

    /// Chainable variant of add_item
    pub fn item(mut self, label: &str, value: T) -> Self {
        self.add_item(label, value);

        self
    }

    /// Wraps this view into an IdView with the given id.
    pub fn with_id(self, label: &str) -> IdView<Self> {
        IdView::new(label, self)
    }
}

impl SelectView<String> {
    /// For String-based SelectView, this is a convenient method to use the label as value.
    pub fn add_item_str(&mut self, label: &str) {
        self.add_item(label, label.to_string());
    }

    /// Chainable variant of add_item_str
    pub fn item_str(self, label: &str) -> Self {
        self.item(label, label.to_string())
    }

}

impl <T> View for SelectView<T> {
    fn draw(&mut self, printer: &Printer) {

        self.scrollbase.draw(printer, |printer,i| {
            let style = if i == self.focus {
                if printer.focused { color::HIGHLIGHT }
                else { color::HIGHLIGHT_INACTIVE }
            } else {
                color::PRIMARY
            };
            printer.with_color(style, |printer| printer.print((0,0), &self.items[i].label));
        });
    }

    fn get_min_size(&self, req: SizeRequest) -> Vec2 {
        let w = self.items.iter().map(|item| item.label.len()).max().unwrap_or(1);
        let h = self.items.len();

        let scrolling = if let DimensionRequest::Fixed(r_h) = req.h {
            r_h < h
        } else if let DimensionRequest::AtMost(r_h) = req.h {
            r_h < h
        } else {
            false
        };

        let w = if scrolling { w + 1 } else { w };

        Vec2::new(w,h)
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        match event {
            Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1,
            Event::KeyEvent(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
            Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus,10),
            Event::KeyEvent(Key::PageDown) => self.focus = min(self.focus+10,self.items.len()-1),
            Event::KeyEvent(Key::Home) => self.focus = 0,
            Event::KeyEvent(Key::End) => self.focus = self.items.len()-1,
            Event::CharEvent(c) => {
                // Starting from the current focus, find the first item that match the char.
                if let Some((i,_)) = self.items.iter().enumerate()
                    .skip(self.focus+1)
                    .find(|&(_,item)| item.label.starts_with(c))
                {
                    self.focus = i;
                }
            },
            _ => return EventResult::Ignored,
        }

        self.scrollbase.scroll_to(self.focus);

        EventResult::Consumed(None)
    }

    fn take_focus(&mut self) -> bool {
        true
    }

    fn layout(&mut self, size: Vec2) {
        self.scrollbase.set_heights(size.y, self.items.len());
    }
}
<commit_msg>Letter-search in SelectView wraps back<commit_after>use std::cmp::min;

use color;
use view::{View,IdView,SizeRequest,DimensionRequest};
use event::{Event,EventResult,Key};
use vec::Vec2;
use printer::Printer;
use super::scroll::ScrollBase;

struct Item<T> {
    label: String,
    value: T,
}

impl <T> Item<T> {
    fn new(label: &str, value: T) -> Self {
        Item {
            label: label.to_string(),
            value: value,
        }
    }
}

/// View to select an item among a list.
///
/// It contains a list of values of type T, with associated labels.
pub struct SelectView<T=String> {
    items: Vec<Item<T>>,
    focus: usize,
    scrollbase: ScrollBase,
}

impl <T> SelectView<T> {
    /// Creates a new empty SelectView.
    pub fn new() -> Self {
        SelectView {
            items: Vec::new(),
            focus: 0,
            scrollbase: ScrollBase::new(),
        }
    }

    /// Returns the value of the currently selected item. Panics if the list is empty.
    pub fn selection(&self) -> &T {
        &self.items[self.focus].value
    }

    /// Adds a item to the list, with given label and value.
    pub fn add_item(&mut self, label: &str, value: T) {
        self.items.push(Item::new(label,value));
    }

    /// Chainable variant of add_item
    pub fn item(mut self, label: &str, value: T) -> Self {
        self.add_item(label, value);

        self
    }

    /// Wraps this view into an IdView with the given id.
    pub fn with_id(self, label: &str) -> IdView<Self> {
        IdView::new(label, self)
    }
}

impl SelectView<String> {
    /// For String-based SelectView, this is a convenient method to use the label as value.
    pub fn add_item_str(&mut self, label: &str) {
        self.add_item(label, label.to_string());
    }

    /// Chainable variant of add_item_str
    pub fn item_str(self, label: &str) -> Self {
        self.item(label, label.to_string())
    }

}

impl <T> View for SelectView<T> {
    fn draw(&mut self, printer: &Printer) {

        self.scrollbase.draw(printer, |printer,i| {
            let style = if i == self.focus {
                if printer.focused { color::HIGHLIGHT }
                else { color::HIGHLIGHT_INACTIVE }
            } else {
                color::PRIMARY
            };
            printer.with_color(style, |printer| printer.print((0,0), &self.items[i].label));
        });
    }

    fn get_min_size(&self, req: SizeRequest) -> Vec2 {
        let w = self.items.iter().map(|item| item.label.len()).max().unwrap_or(1);
        let h = self.items.len();

        let scrolling = if let DimensionRequest::Fixed(r_h) = req.h {
            r_h < h
        } else if let DimensionRequest::AtMost(r_h) = req.h {
            r_h < h
        } else {
            false
        };

        let w = if scrolling { w + 1 } else { w };

        Vec2::new(w,h)
    }

    fn on_event(&mut self, event: Event) -> EventResult {
        match event {
            Event::KeyEvent(Key::Up) if self.focus > 0 => self.focus -= 1,
            Event::KeyEvent(Key::Down) if self.focus + 1 < self.items.len() => self.focus += 1,
            Event::KeyEvent(Key::PageUp) => self.focus -= min(self.focus,10),
            Event::KeyEvent(Key::PageDown) => self.focus = min(self.focus+10,self.items.len()-1),
            Event::KeyEvent(Key::Home) => self.focus = 0,
            Event::KeyEvent(Key::End) => self.focus = self.items.len()-1,
            Event::CharEvent(c) => {
                // Starting from the current focus, find the first item that match the char.
                // Cycle back to the beginning of the list when we reach the end.
                // This is achieved by chaining twice the iterator
                let iter = self.items.iter().chain(self.items.iter());
                if let Some((i,_)) = iter.enumerate()
                    .skip(self.focus+1)
                    .find(|&(_,item)| item.label.starts_with(c))
                {
                    // Apply modulo in case we have a hit from the chained iterator
                    self.focus = i % self.items.len();
                }
            },
            _ => return EventResult::Ignored,
        }

        self.scrollbase.scroll_to(self.focus);

        EventResult::Consumed(None)
    }

    fn take_focus(&mut self) -> bool {
        true
    }

    fn layout(&mut self, size: Vec2) {
        self.scrollbase.set_heights(size.y, self.items.len());
    }
}
<|endoftext|>


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

use crate::sampler::{Address, NativeStack, Registers, Sampler};
use libc;
use mach;
use std::panic;
use std::process;

type MonitoredThreadId = mach::mach_types::thread_act_t;

pub struct MacOsSampler {
    thread_id: MonitoredThreadId,
}

impl MacOsSampler {
    #[allow(unsafe_code)]
    pub fn new() -> Box<Sampler> {
        let thread_id = unsafe { mach::mach_init::mach_thread_self() };
        Box::new(MacOsSampler { thread_id })
    }
}

impl Sampler for MacOsSampler {
    #[allow(unsafe_code)]
    fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
        // Warning: The "critical section" begins here.
        // In the critical section:
        // we must not do any dynamic memory allocation,
        // nor try to acquire any lock
        // or any other unshareable resource.
        let current_hook = panic::take_hook();
        panic::set_hook(Box::new(|_| {
            // Avoiding any allocation or locking as part of standard panicking.
            process::abort();
        }));
        let native_stack = unsafe {
            if let Err(()) = suspend_thread(self.thread_id) {
                panic::set_hook(current_hook);
                return Err(());
            };
            let native_stack = match get_registers(self.thread_id) {
                Ok(regs) => Ok(frame_pointer_stack_walk(regs)),
                Err(()) => Err(()),
            };
            if let Err(()) = resume_thread(self.thread_id) {
                process::abort();
            }
            native_stack
        };
        panic::set_hook(current_hook);
        // NOTE: End of "critical section".
        native_stack
    }
}

fn check_kern_return(kret: mach::kern_return::kern_return_t) -> Result<(), ()> {
    if kret != mach::kern_return::KERN_SUCCESS {
        return Err(());
    }
    Ok(())
}

#[allow(unsafe_code)]
unsafe fn suspend_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
    check_kern_return(mach::thread_act::thread_suspend(thread_id))
}

#[allow(unsafe_code)]
unsafe fn get_registers(thread_id: MonitoredThreadId) -> Result<Registers, ()> {
    let mut state = mach::structs::x86_thread_state64_t::new();
    let mut state_count = mach::structs::x86_thread_state64_t::count();
    let kret = mach::thread_act::thread_get_state(
        thread_id,
        mach::thread_status::x86_THREAD_STATE64,
        (&mut state) as *mut _ as *mut _,
        &mut state_count,
    );
    check_kern_return(kret)?;
    Ok(Registers {
        instruction_ptr: state.__rip as Address,
        stack_ptr: state.__rsp as Address,
        frame_ptr: state.__rbp as Address,
    })
}

#[allow(unsafe_code)]
unsafe fn resume_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
    check_kern_return(mach::thread_act::thread_resume(thread_id))
}

#[allow(unsafe_code)]
unsafe fn frame_pointer_stack_walk(regs: Registers) -> NativeStack {
    // Note: this function will only work with code build with:
    // --dev,
    // or --with-frame-pointer.

    let stackaddr = libc::pthread_get_stackaddr_np(libc::pthread_self());
    let mut native_stack = NativeStack::new();
    let pc = regs.instruction_ptr as *mut std::ffi::c_void;
    let stack = regs.stack_ptr as *mut std::ffi::c_void;
    let _ = native_stack.process_register(pc, stack);
    let mut current = regs.frame_ptr as *mut *mut std::ffi::c_void;
    while !current.is_null() {
        if (current as usize) < stackaddr as usize {
            break;
        }
        let next = *current as *mut *mut std::ffi::c_void;
        let pc = current.add(1);
        let stack = current.add(2);
        if let Err(()) = native_stack.process_register(*pc, *stack) {
            break;
        }
        current = next;
    }
    native_stack
}
<commit_msg>fix frame-pointer stackwalking<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::sampler::{Address, NativeStack, Registers, Sampler};
use libc;
use mach;
use std::panic;
use std::process;

type MonitoredThreadId = mach::mach_types::thread_act_t;

pub struct MacOsSampler {
    thread_id: MonitoredThreadId,
}

impl MacOsSampler {
    #[allow(unsafe_code)]
    pub fn new() -> Box<Sampler> {
        let thread_id = unsafe { mach::mach_init::mach_thread_self() };
        Box::new(MacOsSampler { thread_id })
    }
}

impl Sampler for MacOsSampler {
    #[allow(unsafe_code)]
    fn suspend_and_sample_thread(&self) -> Result<NativeStack, ()> {
        // Warning: The "critical section" begins here.
        // In the critical section:
        // we must not do any dynamic memory allocation,
        // nor try to acquire any lock
        // or any other unshareable resource.
        let current_hook = panic::take_hook();
        panic::set_hook(Box::new(|_| {
            // Avoiding any allocation or locking as part of standard panicking.
            process::abort();
        }));
        let native_stack = unsafe {
            if let Err(()) = suspend_thread(self.thread_id) {
                panic::set_hook(current_hook);
                return Err(());
            };
            let native_stack = match get_registers(self.thread_id) {
                Ok(regs) => Ok(frame_pointer_stack_walk(regs)),
                Err(()) => Err(()),
            };
            if let Err(()) = resume_thread(self.thread_id) {
                process::abort();
            }
            native_stack
        };
        panic::set_hook(current_hook);
        // NOTE: End of "critical section".
        native_stack
    }
}

fn check_kern_return(kret: mach::kern_return::kern_return_t) -> Result<(), ()> {
    if kret != mach::kern_return::KERN_SUCCESS {
        return Err(());
    }
    Ok(())
}

#[allow(unsafe_code)]
unsafe fn suspend_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
    check_kern_return(mach::thread_act::thread_suspend(thread_id))
}

#[allow(unsafe_code)]
unsafe fn get_registers(thread_id: MonitoredThreadId) -> Result<Registers, ()> {
    let mut state = mach::structs::x86_thread_state64_t::new();
    let mut state_count = mach::structs::x86_thread_state64_t::count();
    let kret = mach::thread_act::thread_get_state(
        thread_id,
        mach::thread_status::x86_THREAD_STATE64,
        (&mut state) as *mut _ as *mut _,
        &mut state_count,
    );
    check_kern_return(kret)?;
    Ok(Registers {
        instruction_ptr: state.__rip as Address,
        stack_ptr: state.__rsp as Address,
        frame_ptr: state.__rbp as Address,
    })
}

#[allow(unsafe_code)]
unsafe fn resume_thread(thread_id: MonitoredThreadId) -> Result<(), ()> {
    check_kern_return(mach::thread_act::thread_resume(thread_id))
}

#[allow(unsafe_code)]
unsafe fn frame_pointer_stack_walk(regs: Registers) -> NativeStack {
    // Note: this function will only work with code build with:
    // --dev,
    // or --with-frame-pointer.

    let pthread_t = libc::pthread_self();
    let stackaddr = libc::pthread_get_stackaddr_np(pthread_t);
    let stacksize = libc::pthread_get_stacksize_np(pthread_t);
    let mut native_stack = NativeStack::new();
    let pc = regs.instruction_ptr as *mut std::ffi::c_void;
    let stack = regs.stack_ptr as *mut std::ffi::c_void;
    let _ = native_stack.process_register(pc, stack);
    let mut current = regs.frame_ptr as *mut *mut std::ffi::c_void;
    while !current.is_null() {
        if (current as usize) < stackaddr as usize {
            // Reached the end of the stack.
            break;
        }
        if current as usize >= stackaddr.add(stacksize * 8) as usize {
            // Reached the beginning of the stack.
            // Assumining 64 bit mac(see the stacksize * 8).
            break;
        }
        let next = *current as *mut *mut std::ffi::c_void;
        let pc = current.add(1);
        let stack = current.add(2);
        if let Err(()) = native_stack.process_register(*pc, *stack) {
            break;
        }
        if (next <= current) || (next as usize & 3 != 0) {
            break;
        }
        current = next;
    }
    native_stack
}
<|endoftext|>


<commit_before>extern crate phf;

#[cfg(feature = "flate2")]
extern crate flate2;

use std::borrow::{Borrow, Cow};
use std::io::{self, BufReader, Cursor, Error, ErrorKind, Read};
use std::fs::File;

#[cfg(feature = "flate2")]
use flate2::FlateReadExt;

pub enum Compression {
    None,
    Gzip,
    Passthrough,
}

/// Runtime access to the included files
pub struct Files {
    /// **Do not access this field, it is only public to allow for code generation!**
    pub files: phf::Map<&'static str, (Compression, &'static [u8])>,
}

#[cfg(windows)]
fn as_key(path: &str) -> Cow<str> {
    Cow::Owned(path.replace("\\", "/"))
}

#[cfg(not(windows))]
fn as_key(path: &str) -> Cow<str> {
    Cow::Borrowed(path)
}

impl Files {
    pub fn available(&self, path: &str) -> bool {
        self.files.contains_key(path)
    }

    /// Returns an iterator over all available file names.  Does not
    /// decompress any compressed data.
    pub fn file_names(&self) -> FileNames {
        FileNames { iter: self.files.keys() }
    }

    pub fn get(&self, path: &str) -> io::Result<Cow<'static, [u8]>> {
        let key = as_key(path);
        match self.files.get(key.borrow() as &str) {
            Some(b) => {
                match b.0 {
                    Compression::None => Ok(Cow::Borrowed(b.1)),
                    #[cfg(feature = "flate2")]
                    Compression::Gzip => {
                        let mut r = try!(Cursor::new(b.1).gz_decode());
                        let mut v = Vec::new();
                        try!(r.read_to_end(&mut v));
                        Ok(Cow::Owned(v))
                    }
                    #[cfg(not(feature = "flate2"))]
                    Compression::Gzip => panic!("Feature 'flate2' not enabled"),
                    Compression::Passthrough => {
                        let mut r = BufReader::new(try!(File::open(path)));
                        let mut v = Vec::new();
                        try!(r.read_to_end(&mut v));
                        Ok(Cow::Owned(v))
                    }
                }
            }
            None => Err(Error::new(ErrorKind::NotFound, "Key not found")),
        }
    }

    pub fn read(&self, path: &str) -> io::Result<Box<Read>> {
        let key = as_key(path);
        match self.files.get(key.borrow() as &str) {
            Some(b) => {
                match b.0 {
                    Compression::None => Ok(Box::new(Cursor::new(b.1))),
                    #[cfg(feature = "flate2")]
                    Compression::Gzip => Ok(Box::new(try!(Cursor::new(b.1).gz_decode()))),
                    #[cfg(not(feature = "flate2"))]
                    Compression::Gzip => panic!("Feature 'flate2' not enabled"),
                    Compression::Passthrough => {
                        Ok(Box::new(BufReader::new(try!(File::open(path)))))
                    }
                }
            }
            None => Err(Error::new(ErrorKind::NotFound, "Key not found")),
        }
    }
}

/// Iterates over the file names available for `Files` object.
pub struct FileNames<'a> {
    /// Our internal iterator.  We wrap this in a nice struct so our
    /// caller doesn't need to know the details.
    iter: phf::map::Keys<'a, &'static str, (Compression, &'static [u8])>,
}

impl<'a> Iterator for FileNames<'a> {
    type Item = &'static str;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|s| *s)
    }
}
<commit_msg>Fix minor style issues, remove unnecessary lifetime parameter<commit_after>extern crate phf;

#[cfg(feature = "flate2")]
extern crate flate2;

use std::borrow::{Borrow, Cow};
use std::io::{self, BufReader, Cursor, Error, ErrorKind, Read};
use std::fs::File;

#[cfg(feature = "flate2")]
use flate2::FlateReadExt;

pub enum Compression {
    None,
    Gzip,
    Passthrough,
}

/// Runtime access to the included files
pub struct Files {
    /// **Do not access this field, it is only public to allow for code generation!**
    pub files: phf::Map<&'static str, (Compression, &'static [u8])>,
}

#[cfg(windows)]
fn as_key(path: &str) -> Cow<str> {
    Cow::Owned(path.replace("\\", "/"))
}

#[cfg(not(windows))]
fn as_key(path: &str) -> Cow<str> {
    Cow::Borrowed(path)
}

impl Files {
    pub fn available(&self, path: &str) -> bool {
        self.files.contains_key(path)
    }

    /// Returns an iterator over all available file names.  Does not
    /// decompress any compressed data.
    pub fn file_names(&'static self) -> FileNames {
        FileNames { iter: self.files.keys() }
    }

    pub fn get(&self, path: &str) -> io::Result<Cow<'static, [u8]>> {
        let key = as_key(path);
        match self.files.get(key.borrow() as &str) {
            Some(b) => {
                match b.0 {
                    Compression::None => Ok(Cow::Borrowed(b.1)),
                    #[cfg(feature = "flate2")]
                    Compression::Gzip => {
                        let mut r = try!(Cursor::new(b.1).gz_decode());
                        let mut v = Vec::new();
                        try!(r.read_to_end(&mut v));
                        Ok(Cow::Owned(v))
                    }
                    #[cfg(not(feature = "flate2"))]
                    Compression::Gzip => panic!("Feature 'flate2' not enabled"),
                    Compression::Passthrough => {
                        let mut r = BufReader::new(try!(File::open(path)));
                        let mut v = Vec::new();
                        try!(r.read_to_end(&mut v));
                        Ok(Cow::Owned(v))
                    }
                }
            }
            None => Err(Error::new(ErrorKind::NotFound, "Key not found")),
        }
    }

    pub fn read(&self, path: &str) -> io::Result<Box<Read>> {
        let key = as_key(path);
        match self.files.get(key.borrow() as &str) {
            Some(b) => {
                match b.0 {
                    Compression::None => Ok(Box::new(Cursor::new(b.1))),
                    #[cfg(feature = "flate2")]
                    Compression::Gzip => Ok(Box::new(try!(Cursor::new(b.1).gz_decode()))),
                    #[cfg(not(feature = "flate2"))]
                    Compression::Gzip => panic!("Feature 'flate2' not enabled"),
                    Compression::Passthrough => {
                        Ok(Box::new(BufReader::new(try!(File::open(path)))))
                    }
                }
            }
            None => Err(Error::new(ErrorKind::NotFound, "Key not found")),
        }
    }
}

/// Iterates over the file names available for `Files` object.
pub struct FileNames {
    // Our internal iterator.  We wrap this in a nice struct so our
    // caller doesn't need to know the details.
    iter: phf::map::Keys<'static, &'static str, (Compression, &'static [u8])>,
}

impl Iterator for FileNames {
    type Item = &'static str;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|s| *s)
    }
}
<|endoftext|>


<commit_before>import ptr::addr_of;
import comm::{port, chan, methods};
import result::{result, ok, err};
import std::net::ip::{
    get_addr, format_addr, ipv4, ipv6, ip_addr,
    ip_get_addr_err
};
import std::net::tcp::{connect, tcp_socket};
import std::uv_global_loop;
import comm::{methods};
import connection::{
    Connection, ConnectionFactory, UvConnectionFactory,
    MockConnection, MockConnectionFactory
};
import parser::{Parser, ParserCallbacks};

const timeout: uint = 2000;

/**
A quick hack URI type
*/
type Uri = {
    host: ~str,
    path: ~str
};

/// HTTP status codes
enum StatusCode {
    StatusOk = 200,
    StatusFound = 302,
    StatusUnknown
}

/// HTTP request error conditions
enum RequestError {
    ErrorDnsResolution,
    ErrorConnect,
    ErrorMisc
}

/// Request
enum RequestEvent {
    Status(StatusCode),
    Payload(~mut option<~[u8]>),
    Error(RequestError)
}

type DnsResolver = fn@(host: ~str) -> result<~[ip_addr], ip_get_addr_err>;

fn uv_dns_resolver() -> DnsResolver {
    |host| {
        let iotask = uv_global_loop::get();
        get_addr(host, iotask)
    }
}

fn uv_http_request(+uri: Uri) -> HttpRequest<tcp_socket, UvConnectionFactory> {
    HttpRequest(uv_dns_resolver(), UvConnectionFactory, uri)
}

class HttpRequest<C: Connection, CF: ConnectionFactory<C>> {

    let resolve_ip_addr: DnsResolver;
    let connection_factory: CF;
    let uri: Uri;
    let parser: Parser;
    let mut cb: fn@(+RequestEvent);

    new(resolver: DnsResolver, +connection_factory: CF, +uri: Uri) {
        self.resolve_ip_addr = resolver;
        self.connection_factory = connection_factory;
        self.uri = uri;
        self.parser = Parser();
        self.cb = |_event| { };
    }

    fn begin(cb: fn@(+RequestEvent)) {
        #debug("http_client: looking up uri %?", self.uri);
        let ip_addr = alt self.get_ip() {
          ok(ip_addr) { ip_addr }
          err(e) { cb(Error(e)); ret }
        };

        #debug("http_client: using IP %? for %?", format_addr(ip_addr), self.uri);

        let socket = {
            #debug("http_client: connecting to %?", ip_addr);
            let socket = self.connection_factory.connect(copy ip_addr, 80);
            if socket.is_ok() {
                result::unwrap(socket)
            } else {
                #debug("http_client: unable to connect to %?: %?", ip_addr, socket);
                cb(Error(ErrorConnect));
                ret;
            }
        };

        #debug("http_client: got socket for %?", ip_addr);

        let request_header = #fmt("GET %s HTTP/1.0\u000D\u000AHost: %s\u000D\u000A\u000D\u000A",
                                  self.uri.path, self.uri.host);
        #debug("http_client: writing request header: %?", request_header);
        let request_header_bytes = str::bytes(request_header);
        alt socket.write_(request_header_bytes) {
          result::ok(*) { }
          result::err(e) {
            // FIXME: Need test
            cb(Error(ErrorMisc));
            ret;
          }
        }

        let read_port = {
            let read_port = socket.read_start_();
            if read_port.is_ok() {
                result::unwrap(read_port)
            } else {
                cb(Error(ErrorMisc));
                ret;
            }
        };

        // This unsafety is unfortunate but we can't capture self
        // into shared closures
        let unsafe_self = addr_of(self);
        let callbacks: ParserCallbacks = unsafe {{
            on_message_begin: || (*unsafe_self).on_message_begin(),
            on_url: |data| (*unsafe_self).on_url(data),
            on_header_field: |data| (*unsafe_self).on_header_field(data),
            on_header_value: |data| (*unsafe_self).on_header_value(data),
            on_headers_complete: || (*unsafe_self).on_headers_complete(),
            on_body: |data| (*unsafe_self).on_body(data),
            on_message_complete: || (*unsafe_self).on_message_complete()
        }};

        // Set the callback used by the parser event handlers
        self.cb = cb;

        loop {
            let next_data = read_port.recv();

            if next_data.is_ok() {
                let next_data = next_data.get();
                #debug("data: %?", next_data);
                let bytes_parsed = self.parser.execute(next_data, &callbacks);
                if bytes_parsed != next_data.len() {
                    // FIXME: Need tests
                    fail ~"http parse failure";
                }
            } else {
                #debug("http_client: read error: %?", next_data);

                // This method of detecting EOF is lame
                alt next_data {
                  result::err({err_name: ~"EOF", _}) {
                    self.parser.execute(~[], &callbacks);
                    break;
                  }
                  _ {
                    // FIXME: Need tests and error handling
                    socket.read_stop_(read_port);
                    cb(Error(ErrorMisc));
                    ret;
                  }
                }
            }
        }
        socket.read_stop_(read_port);
    }

    fn get_ip() -> result<ip_addr, RequestError> {
        let ip_addrs = self.resolve_ip_addr(self.uri.host);
        if ip_addrs.is_ok() {
            let ip_addrs = result::unwrap(ip_addrs);
            // FIXME: This log crashes
            //#debug("http_client: got IP addresses for %?: %?", self.uri, ip_addrs);
            if ip_addrs.is_not_empty() {
                // FIXME: Which address should we really pick?
                let best_ip = do ip_addrs.find |ip| {
                    alt ip {
                      ipv4(*) { true }
                      ipv6(*) { false }
                    }
                };

                if best_ip.is_some() {
                    ret ok(option::unwrap(best_ip));
                } else {
                    // FIXME: Need test
                    ret err(ErrorMisc);
                }
            } else {
                #debug("http_client: got no IP addresses for %?", self.uri);
                // FIXME: Need test
                ret err(ErrorMisc);
            }
        } else {
            #debug("http_client: DNS lookup failure: %?", ip_addrs.get_err());
            ret err(ErrorDnsResolution);
        }
    }

    fn on_message_begin() -> bool {
        #debug("on_message_begin");
        true
    }

    fn on_url(+data: ~[u8]) -> bool {
        #debug("on_url");
        true
    }

    fn on_header_field(+data: ~[u8]) -> bool {
        let header_field = str::from_bytes(data);
        #debug("on_header_field: %?", header_field);
        true
    }

    fn on_header_value(+data: ~[u8]) -> bool {
        let header_value = str::from_bytes(data);
        #debug("on_header_value: %?", header_value);
        true
    }

    fn on_headers_complete() -> bool {
        #debug("on_headers_complete");
        true
    }

    fn on_body(+data: ~[u8]) -> bool {
        #debug("on_body");
        let the_payload = Payload(~mut some(data));
        self.cb(the_payload);
        true
    }

    fn on_message_complete() -> bool {
        #debug("on_message_complete");
        true
    }
}

fn sequence<C: Connection, CF: ConnectionFactory<C>>(request: HttpRequest<C, CF>) -> ~[RequestEvent] {
    let events = @mut ~[];
    do request.begin |event| {
        vec::push(*events, event)
    }
    ret *events;
}

#[test]
fn test_resolve_error() {
    let uri = {
        host: ~"example.com_not_real",
        path: ~"/"
    };

    let request = uv_http_request(uri);
    let events = sequence(request);

    assert events == ~[
        Error(ErrorDnsResolution),
    ];
}

#[test]
fn test_connect_error() {
    let uri = {
        // This address is invalid because the first octet
        // of a class A address cannot be 0
        host: ~"0.42.42.42",
        path: ~"/"
    };

    let request = uv_http_request(uri);
    let events = sequence(request);

    assert events == ~[
        Error(ErrorConnect),
    ];
}

#[test]
fn test_connect_success() {
    let uri = {
        host: ~"example.com",
        path: ~"/"
    };

    let request = uv_http_request(uri);
    let events = sequence(request);

    for events.each |ev| {
        alt ev {
          Error(*) { fail }
          _ { }
        }
    }
}

#[test]
fn test_simple_body() {
    let uri = {
        host: ~"www.iana.org",
        path: ~"/"
    };

    let request = uv_http_request(uri);
    let events = sequence(request);

    let mut found = false;

    for events.each |ev| {
        alt ev {
          Payload(value) {
            if str::from_bytes(value.get()).contains(~"DOCTYPE html") {
                found = true
            }
          }
          _ { }
        }
    }

    assert found;
}

#[test]
#[ignore(reason = "ICE")]
fn test_simple_response() {
    let uri = {
        host: ~"whatever",
        path: ~"/"
    };

    let mock_connection: MockConnection = {
        write_fn: |data| { ok(()) },
        read_start_fn: || {
            let port = port();
            let chan = port.chan();

            let response = ~"HTTP/1.0 200 OK\
                            \
                            Test";
            chan.send(ok(str::bytes(response)));

            ok(port)
        },
        read_stop_fn: |_port| { ok(()) }
    };

    let mock_connection_factory: MockConnectionFactory = {
        connect_fn: |ip, port| {

            // FIXME this doesn't work
            fail;//ok(mock_connection)
        }
    };
}<commit_msg>Use std::net::url<commit_after>import to_str::to_str;
import ptr::addr_of;
import comm::{port, chan, methods};
import result::{result, ok, err};
import std::net::ip::{
    get_addr, format_addr, ipv4, ipv6, ip_addr,
    ip_get_addr_err
};
import std::net::tcp::{connect, tcp_socket};
import std::net::url;
import std::net::url::url;
import std::uv_global_loop;
import comm::{methods};
import connection::{
    Connection, ConnectionFactory, UvConnectionFactory,
    MockConnection, MockConnectionFactory
};
import parser::{Parser, ParserCallbacks};

const timeout: uint = 2000;

/// HTTP status codes
enum StatusCode {
    StatusOk = 200,
    StatusFound = 302,
    StatusUnknown
}

/// HTTP request error conditions
enum RequestError {
    ErrorDnsResolution,
    ErrorConnect,
    ErrorMisc
}

/// Request
enum RequestEvent {
    Status(StatusCode),
    Payload(~mut option<~[u8]>),
    Error(RequestError)
}

type DnsResolver = fn@(host: ~str) -> result<~[ip_addr], ip_get_addr_err>;

fn uv_dns_resolver() -> DnsResolver {
    |host| {
        let iotask = uv_global_loop::get();
        get_addr(host, iotask)
    }
}

fn uv_http_request(+url: url) -> HttpRequest<tcp_socket, UvConnectionFactory> {
    HttpRequest(uv_dns_resolver(), UvConnectionFactory, url)
}

class HttpRequest<C: Connection, CF: ConnectionFactory<C>> {

    let resolve_ip_addr: DnsResolver;
    let connection_factory: CF;
    let url: url;
    let parser: Parser;
    let mut cb: fn@(+RequestEvent);

    new(resolver: DnsResolver, +connection_factory: CF, +url: url) {
        self.resolve_ip_addr = resolver;
        self.connection_factory = connection_factory;
        self.url = url;
        self.parser = Parser();
        self.cb = |_event| { };
    }

    fn begin(cb: fn@(+RequestEvent)) {
        #debug("http_client: looking up url %?", self.url.to_str());
        let ip_addr = alt self.get_ip() {
          ok(ip_addr) { ip_addr }
          err(e) { cb(Error(e)); ret }
        };

        #debug("http_client: using IP %? for %?", format_addr(ip_addr), self.url.to_str());

        let socket = {
            #debug("http_client: connecting to %?", ip_addr);
            let socket = self.connection_factory.connect(copy ip_addr, 80);
            if socket.is_ok() {
                result::unwrap(socket)
            } else {
                #debug("http_client: unable to connect to %?: %?", ip_addr, socket);
                cb(Error(ErrorConnect));
                ret;
            }
        };

        #debug("http_client: got socket for %?", ip_addr);

        let request_header = #fmt("GET %s HTTP/1.0\u000D\u000AHost: %s\u000D\u000A\u000D\u000A",
                                  self.url.path, self.url.host);
        #debug("http_client: writing request header: %?", request_header);
        let request_header_bytes = str::bytes(request_header);
        alt socket.write_(request_header_bytes) {
          result::ok(*) { }
          result::err(e) {
            // FIXME: Need test
            cb(Error(ErrorMisc));
            ret;
          }
        }

        let read_port = {
            let read_port = socket.read_start_();
            if read_port.is_ok() {
                result::unwrap(read_port)
            } else {
                cb(Error(ErrorMisc));
                ret;
            }
        };

        // This unsafety is unfortunate but we can't capture self
        // into shared closures
        let unsafe_self = addr_of(self);
        let callbacks: ParserCallbacks = unsafe {{
            on_message_begin: || (*unsafe_self).on_message_begin(),
            on_url: |data| (*unsafe_self).on_url(data),
            on_header_field: |data| (*unsafe_self).on_header_field(data),
            on_header_value: |data| (*unsafe_self).on_header_value(data),
            on_headers_complete: || (*unsafe_self).on_headers_complete(),
            on_body: |data| (*unsafe_self).on_body(data),
            on_message_complete: || (*unsafe_self).on_message_complete()
        }};

        // Set the callback used by the parser event handlers
        self.cb = cb;

        loop {
            let next_data = read_port.recv();

            if next_data.is_ok() {
                let next_data = next_data.get();
                #debug("data: %?", next_data);
                let bytes_parsed = self.parser.execute(next_data, &callbacks);
                if bytes_parsed != next_data.len() {
                    // FIXME: Need tests
                    fail ~"http parse failure";
                }
            } else {
                #debug("http_client: read error: %?", next_data);

                // This method of detecting EOF is lame
                alt next_data {
                  result::err({err_name: ~"EOF", _}) {
                    self.parser.execute(~[], &callbacks);
                    break;
                  }
                  _ {
                    // FIXME: Need tests and error handling
                    socket.read_stop_(read_port);
                    cb(Error(ErrorMisc));
                    ret;
                  }
                }
            }
        }
        socket.read_stop_(read_port);
    }

    fn get_ip() -> result<ip_addr, RequestError> {
        let ip_addrs = self.resolve_ip_addr(self.url.host);
        if ip_addrs.is_ok() {
            let ip_addrs = result::unwrap(ip_addrs);
            // FIXME: This log crashes
            //#debug("http_client: got IP addresses for %?: %?", self.url, ip_addrs);
            if ip_addrs.is_not_empty() {
                // FIXME: Which address should we really pick?
                let best_ip = do ip_addrs.find |ip| {
                    alt ip {
                      ipv4(*) { true }
                      ipv6(*) { false }
                    }
                };

                if best_ip.is_some() {
                    ret ok(option::unwrap(best_ip));
                } else {
                    // FIXME: Need test
                    ret err(ErrorMisc);
                }
            } else {
                #debug("http_client: got no IP addresses for %?", self.url);
                // FIXME: Need test
                ret err(ErrorMisc);
            }
        } else {
            #debug("http_client: DNS lookup failure: %?", ip_addrs.get_err());
            ret err(ErrorDnsResolution);
        }
    }

    fn on_message_begin() -> bool {
        #debug("on_message_begin");
        true
    }

    fn on_url(+data: ~[u8]) -> bool {
        #debug("on_url");
        true
    }

    fn on_header_field(+data: ~[u8]) -> bool {
        let header_field = str::from_bytes(data);
        #debug("on_header_field: %?", header_field);
        true
    }

    fn on_header_value(+data: ~[u8]) -> bool {
        let header_value = str::from_bytes(data);
        #debug("on_header_value: %?", header_value);
        true
    }

    fn on_headers_complete() -> bool {
        #debug("on_headers_complete");
        true
    }

    fn on_body(+data: ~[u8]) -> bool {
        #debug("on_body");
        let the_payload = Payload(~mut some(data));
        self.cb(the_payload);
        true
    }

    fn on_message_complete() -> bool {
        #debug("on_message_complete");
        true
    }
}

fn sequence<C: Connection, CF: ConnectionFactory<C>>(request: HttpRequest<C, CF>) -> ~[RequestEvent] {
    let events = @mut ~[];
    do request.begin |event| {
        vec::push(*events, event)
    }
    ret *events;
}

#[test]
fn test_resolve_error() {
    let url = url::from_str(~"http://example.com_not_real/").get();
    let request = uv_http_request(url);
    let events = sequence(request);

    assert events == ~[
        Error(ErrorDnsResolution),
    ];
}

#[test]
fn test_connect_error() {
    // This address is invalid because the first octet
    // of a class A address cannot be 0
    let url = url::from_str(~"http://0.42.42.42/").get();
    let request = uv_http_request(url);
    let events = sequence(request);

    assert events == ~[
        Error(ErrorConnect),
    ];
}

#[test]
fn test_connect_success() {
    let url = url::from_str(~"http://example.com/").get();
    let request = uv_http_request(url);
    let events = sequence(request);

    for events.each |ev| {
        alt ev {
          Error(*) { fail }
          _ { }
        }
    }
}

#[test]
fn test_simple_body() {
    let url = url::from_str(~"http://www.iana.org/").get();
    let request = uv_http_request(url);
    let events = sequence(request);

    let mut found = false;

    for events.each |ev| {
        alt ev {
          Payload(value) {
            if str::from_bytes(value.get()).contains(~"DOCTYPE html") {
                found = true
            }
          }
          _ { }
        }
    }

    assert found;
}

#[test]
#[ignore(reason = "ICE")]
fn test_simple_response() {
    let url = url::from_str(~"http://whatever/").get();
    let mock_connection: MockConnection = {
        write_fn: |data| { ok(()) },
        read_start_fn: || {
            let port = port();
            let chan = port.chan();

            let response = ~"HTTP/1.0 200 OK\
                            \
                            Test";
            chan.send(ok(str::bytes(response)));

            ok(port)
        },
        read_stop_fn: |_port| { ok(()) }
    };

    let mock_connection_factory: MockConnectionFactory = {
        connect_fn: |ip, port| {

            // FIXME this doesn't work
            fail;//ok(mock_connection)
        }
    };
}<|endoftext|>


<commit_before>//! Things pertaining to the RFC 7230 `token` grammar rule.
//!
//! RFC 7230 grammar:
//!
//! ```abnf
//! token          = 1*tchar
//!
//! tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
//!                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
//!                / DIGIT / ALPHA
//!                ; any VCHAR, except delimiters
//! ```
//!
//! Possibly of interest is that RFC 2616 actually defined things the other way
//! round, with the primary definition being that of what is now labelled the
//! delimiters but which was at that time known as *separators*:
//!
//! ```bnf
//! [REMEMBER THIS RULE IS OBSOLETE!]
//! separators: "(" | ")" | "<" | ">" | "@" | "," | ";" | ":"
//!           | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{"
//!           | "}" | SP | HT
//! ```

use std::fmt;
use std::str;
use self::Token::{Owned, Slice};

/// tchar: a token character; any VCHAR, except delimiters.
#[inline]
pub fn is_tchar(o: u8) -> bool {
    o == b'!' || o == b'#' || o == b'$' || o == b'%' || o == b'&' || o == b'\'' ||
    o == b'*' || o == b'+' || o == b'-' || o == b'.' || o == b'^' || o == b'_' ||
    o == b'`' || o == b'|' || o == b'~' || (o >= b'0' && o <= b'9') ||
    (o >= b'A' && o <= b'Z') || (o >= b'a' && o <= b'z')
}

/// A type representing an RFC 7230 `token`.
///
/// This permits strict character set control in a way that a simple `Vec<u8>`
/// or `String` would not.
///
/// This may be either owned, corresponding to `String`/`Vec<u8>`, or a slice,
/// corresponding to `&str`/`&[u8]`.
#[deriving(Clone, Hash)]
pub enum Token<'a> {
    /// A token backed by a vector (`Vec<u8>`).
    #[doc(hidden)]
    Owned {
        #[doc(hidden)]
        _bytes: Vec<u8>,
    },
    /// A token backed by a slice (`&[u8]`).
    #[doc(hidden)]
    Slice {
        #[doc(hidden)]
        _bytes: &'a [u8],
    },
}

impl<'a> fmt::Show for Token<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Owned { ref _bytes } => f.write(_bytes.as_slice()),
            Slice { ref _bytes } => f.write(*_bytes),
        }
    }
}

impl<'a> PartialOrd for Token<'a> {
    #[inline]
    fn partial_cmp(&self, other: &Token<'a>) -> Option<Ordering> {
        self.as_bytes().partial_cmp(other.as_bytes())
    }
}

impl<'a> Ord for Token<'a> {
    #[inline]
    fn cmp(&self, other: &Token<'a>) -> Ordering {
        self.as_bytes().cmp(other.as_bytes())
    }
}

impl<'a> PartialEq for Token<'a> {
    #[inline]
    fn eq(&self, other: &Token<'a>) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl<'a> Eq for Token<'a> { }

impl<'a> Token<'a> {
    /// The number of bytes in the token.
    #[inline]
    pub fn len(&self) -> uint {
        self.as_bytes().len()
    }

    /// Whether the token is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Token<'static> {
    /// Create a `Token` from a sequence of bytes.
    ///
    /// Returns `Err` with the original vector if not every byte in the vector
    /// is an RFC 7230 `tchar`.
    #[inline]
    pub fn from_vec(vec: Vec<u8>) -> Result<Token<'static>, Vec<u8>> {
        if vec.iter().all(|&c| is_tchar(c)) {
            Ok(Owned { _bytes: vec })
        } else {
            Err(vec)
        }
    }

    /// Create a `Token` from a sequence of bytes, without checking it.
    ///
    /// Be very careful calling this.
    #[inline]
    pub unsafe fn from_vec_nocheck(vec: Vec<u8>) -> Token<'static> {
        Owned { _bytes: vec }
    }
}

impl<'a> Token<'a> {
    /// Create a `Token` from a sequence of bytes.
    ///
    /// Returns `None` if not every byte in the slice is a RFC 7230 `tchar`.
    pub fn from_slice(slice: &[u8]) -> Option<Token> {
        if slice.iter().all(|&c| is_tchar(c)) {
            Some(Slice { _bytes: slice })
        } else {
            None
        }
    }

    /// Create a `Token` from a sequence of bytes, without checking it.
    ///
    /// Be very careful calling this.
    pub unsafe fn from_slice_nocheck(slice: &[u8]) -> Token {
        Slice { _bytes: slice }
    }

    /// Make a copy of the token, based around a slice of `self`.
    ///
    /// This is practically a free operation.
    #[inline]
    pub fn slice(&self) -> Token {
        Slice { _bytes: self.as_bytes() }
    }

    /// Change a slice token into an owned token.
    ///
    /// An owned token will be unchanged.
    #[inline]
    pub fn into_owned(self) -> Token<'static> {
        match self {
            Owned { _bytes } => Owned { _bytes: _bytes },
            Slice { _bytes } => Owned { _bytes: _bytes.to_vec() },
        }
    }

    /// Get a string slice of the contents of the token.
    #[inline]
    pub fn as_str(&self) -> &str {
        // `token` is a subset of ASCII, so this cannot produce invalid data.
        unsafe {
            str::raw::from_utf8(self.as_bytes())
        }
    }

    /// Get a slice of the bytes in the token.
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        match *self {
            Owned { ref _bytes } => _bytes.as_slice(),
            Slice { ref _bytes } => _bytes.as_slice(),
        }
    }
}

impl<'a> Deref<str> for Token<'a> {
    fn deref(&self) -> &str {
        self.as_str()
    }
}
<commit_msg>Implement appropriate BorrowFrom stuff for Token.<commit_after>//! Things pertaining to the RFC 7230 `token` grammar rule.
//!
//! RFC 7230 grammar:
//!
//! ```abnf
//! token          = 1*tchar
//!
//! tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
//!                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
//!                / DIGIT / ALPHA
//!                ; any VCHAR, except delimiters
//! ```
//!
//! Possibly of interest is that RFC 2616 actually defined things the other way
//! round, with the primary definition being that of what is now labelled the
//! delimiters but which was at that time known as *separators*:
//!
//! ```bnf
//! [REMEMBER THIS RULE IS OBSOLETE!]
//! separators: "(" | ")" | "<" | ">" | "@" | "," | ";" | ":"
//!           | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{"
//!           | "}" | SP | HT
//! ```

use std::borrow::BorrowFrom;
use std::fmt;
use std::str;
use self::Token::{Owned, Slice};

/// tchar: a token character; any VCHAR, except delimiters.
#[inline]
pub fn is_tchar(o: u8) -> bool {
    o == b'!' || o == b'#' || o == b'$' || o == b'%' || o == b'&' || o == b'\'' ||
    o == b'*' || o == b'+' || o == b'-' || o == b'.' || o == b'^' || o == b'_' ||
    o == b'`' || o == b'|' || o == b'~' || (o >= b'0' && o <= b'9') ||
    (o >= b'A' && o <= b'Z') || (o >= b'a' && o <= b'z')
}

/// A type representing an RFC 7230 `token`.
///
/// This permits strict character set control in a way that a simple `Vec<u8>`
/// or `String` would not.
///
/// This may be either owned, corresponding to `String`/`Vec<u8>`, or a slice,
/// corresponding to `&str`/`&[u8]`.
#[deriving(Clone, Hash)]
pub enum Token<'a> {
    /// A token backed by a vector (`Vec<u8>`).
    #[doc(hidden)]
    Owned {
        #[doc(hidden)]
        _bytes: Vec<u8>,
    },
    /// A token backed by a slice (`&[u8]`).
    #[doc(hidden)]
    Slice {
        #[doc(hidden)]
        _bytes: &'a [u8],
    },
}

impl<'a> fmt::Show for Token<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Owned { ref _bytes } => f.write(_bytes.as_slice()),
            Slice { ref _bytes } => f.write(*_bytes),
        }
    }
}

impl<'a> PartialOrd for Token<'a> {
    #[inline]
    fn partial_cmp(&self, other: &Token<'a>) -> Option<Ordering> {
        self.as_bytes().partial_cmp(other.as_bytes())
    }
}

impl<'a> Ord for Token<'a> {
    #[inline]
    fn cmp(&self, other: &Token<'a>) -> Ordering {
        self.as_bytes().cmp(other.as_bytes())
    }
}

impl<'a> PartialEq for Token<'a> {
    #[inline]
    fn eq(&self, other: &Token<'a>) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

impl<'a> Eq for Token<'a> { }

impl<'a> Token<'a> {
    /// The number of bytes in the token.
    #[inline]
    pub fn len(&self) -> uint {
        self.as_bytes().len()
    }

    /// Whether the token is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Token<'static> {
    /// Create a `Token` from a sequence of bytes.
    ///
    /// Returns `Err` with the original vector if not every byte in the vector
    /// is an RFC 7230 `tchar`.
    #[inline]
    pub fn from_vec(vec: Vec<u8>) -> Result<Token<'static>, Vec<u8>> {
        if vec.iter().all(|&c| is_tchar(c)) {
            Ok(Owned { _bytes: vec })
        } else {
            Err(vec)
        }
    }

    /// Create a `Token` from a sequence of bytes, without checking it.
    ///
    /// Be very careful calling this.
    #[inline]
    pub unsafe fn from_vec_nocheck(vec: Vec<u8>) -> Token<'static> {
        Owned { _bytes: vec }
    }
}

impl<'a> Token<'a> {
    /// Create a `Token` from a sequence of bytes.
    ///
    /// Returns `None` if not every byte in the slice is a RFC 7230 `tchar`.
    pub fn from_slice(slice: &[u8]) -> Option<Token> {
        if slice.iter().all(|&c| is_tchar(c)) {
            Some(Slice { _bytes: slice })
        } else {
            None
        }
    }

    /// Create a `Token` from a sequence of bytes, without checking it.
    ///
    /// Be very careful calling this.
    pub unsafe fn from_slice_nocheck(slice: &[u8]) -> Token {
        Slice { _bytes: slice }
    }

    /// Make a copy of the token, based around a slice of `self`.
    ///
    /// This is practically a free operation.
    #[inline]
    pub fn slice(&self) -> Token {
        Slice { _bytes: self.as_bytes() }
    }

    /// Change a slice token into an owned token.
    ///
    /// An owned token will be unchanged.
    #[inline]
    pub fn into_owned(self) -> Token<'static> {
        match self {
            Owned { _bytes } => Owned { _bytes: _bytes },
            Slice { _bytes } => Owned { _bytes: _bytes.to_vec() },
        }
    }

    /// Get a string slice of the contents of the token.
    #[inline]
    pub fn as_str(&self) -> &str {
        // `token` is a subset of ASCII, so this cannot produce invalid data.
        unsafe {
            str::raw::from_utf8(self.as_bytes())
        }
    }

    /// Get a slice of the bytes in the token.
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        match *self {
            Owned { ref _bytes } => _bytes.as_slice(),
            Slice { ref _bytes } => _bytes.as_slice(),
        }
    }
}

impl<'a> Deref<str> for Token<'a> {
    fn deref(&self) -> &str {
        self.as_str()
    }
}

impl<'a> BorrowFrom<Token<'a>> for [u8] {
    fn borrow_from<'b>(owned: &'b Token<'a>) -> &'b [u8] { owned.as_bytes() }
}

impl<'a> BorrowFrom<Token<'a>> for str {
    fn borrow_from<'b>(owned: &'b Token<'a>) -> &'b str { owned.as_str() }
}
<|endoftext|>