<commit_before>// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Interfaces to the operating system provided random number
//! generators.

pub use self::imp::OsRng;

#[cfg(all(unix, not(target_os = "ios")))]
mod imp {
    extern crate libc;

    use self::OsRngInner::*;

    use io::{IoResult, File};
    use path::Path;
    use rand::Rng;
    use rand::reader::ReaderRng;
    use result::Result::{Ok, Err};
    use slice::SlicePrelude;
    use mem;
    use os::errno;

    #[cfg(all(target_os = "linux",
              any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
    fn getrandom(buf: &mut [u8]) -> libc::c_long {
        extern "C" {
            fn syscall(number: libc::c_long, ...) -> libc::c_long;
        }

        #[cfg(target_arch = "x86_64")]
        const NR_GETRANDOM: libc::c_long = 318;
        #[cfg(target_arch = "x86")]
        const NR_GETRANDOM: libc::c_long = 355;
        #[cfg(target_arch = "arm")]
        const NR_GETRANDOM: libc::c_long = 384;

        unsafe {
            syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0u)
        }
    }

    #[cfg(not(all(target_os = "linux",
                  any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
    fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }

    fn getrandom_fill_bytes(v: &mut [u8]) {
        let mut read = 0;
        let len = v.len();
        while read < len {
            let result = getrandom(v[mut read..]);
            if result == -1 {
                let err = errno() as libc::c_int;
                if err == libc::EINTR {
                    continue;
                } else {
                    panic!("unexpected getrandom error: {}", err);
                }
            } else {
                read += result as uint;
            }
        }
    }

    fn getrandom_next_u32() -> u32 {
        let mut buf: [u8, ..4] = [0u8, ..4];
        getrandom_fill_bytes(&mut buf);
        unsafe { mem::transmute::<[u8, ..4], u32>(buf) }
    }

    fn getrandom_next_u64() -> u64 {
        let mut buf: [u8, ..8] = [0u8, ..8];
        getrandom_fill_bytes(&mut buf);
        unsafe { mem::transmute::<[u8, ..8], u64>(buf) }
    }

    #[cfg(all(target_os = "linux",
              any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
    fn is_getrandom_available() -> bool {
        use sync::atomic::{AtomicBool, INIT_ATOMIC_BOOL, Relaxed};

        static GETRANDOM_CHECKED: AtomicBool = INIT_ATOMIC_BOOL;
        static GETRANDOM_AVAILABLE: AtomicBool = INIT_ATOMIC_BOOL;

        if !GETRANDOM_CHECKED.load(Relaxed) {
            let mut buf: [u8, ..0] = [];
            let result = getrandom(&mut buf);
            let available = if result == -1 {
                let err = errno() as libc::c_int;
                err != libc::ENOSYS
            } else {
                true
            };
            GETRANDOM_AVAILABLE.store(available, Relaxed);
            GETRANDOM_CHECKED.store(true, Relaxed);
            available
        } else {
            GETRANDOM_AVAILABLE.load(Relaxed)
        }
    }

    #[cfg(not(all(target_os = "linux",
                  any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
    fn is_getrandom_available() -> bool { false }

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
    /// This does not block.
    pub struct OsRng {
        inner: OsRngInner,
    }

    enum OsRngInner {
        OsGetrandomRng,
        OsReaderRng(ReaderRng<File>),
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            if is_getrandom_available() {
                return Ok(OsRng { inner: OsGetrandomRng });
            }

            let reader = try!(File::open(&Path::new("/dev/urandom")));
            let reader_rng = ReaderRng::new(reader);

            Ok(OsRng { inner: OsReaderRng(reader_rng) })
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            match self.inner {
                OsGetrandomRng => getrandom_next_u32(),
                OsReaderRng(ref mut rng) => rng.next_u32(),
            }
        }
        fn next_u64(&mut self) -> u64 {
            match self.inner {
                OsGetrandomRng => getrandom_next_u64(),
                OsReaderRng(ref mut rng) => rng.next_u64(),
            }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            match self.inner {
                OsGetrandomRng => getrandom_fill_bytes(v),
                OsReaderRng(ref mut rng) => rng.fill_bytes(v)
            }
        }
    }
}

#[cfg(target_os = "ios")]
mod imp {
    extern crate libc;

    use io::{IoResult};
    use kinds::marker;
    use mem;
    use os;
    use rand::Rng;
    use result::Result::{Ok};
    use self::libc::{c_int, size_t};
    use slice::{SlicePrelude};

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
    /// This does not block.
    pub struct OsRng {
        marker: marker::NoCopy
    }

    #[repr(C)]
    struct SecRandom;

    #[allow(non_upper_case_globals)]
    static kSecRandomDefault: *const SecRandom = 0 as *const SecRandom;

    #[link(name = "Security", kind = "framework")]
    extern "C" {
        fn SecRandomCopyBytes(rnd: *const SecRandom,
                              count: size_t, bytes: *mut u8) -> c_int;
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            Ok(OsRng {marker: marker::NoCopy} )
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            let mut v = [0u8, .. 4];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn next_u64(&mut self) -> u64 {
            let mut v = [0u8, .. 8];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            let ret = unsafe {
                SecRandomCopyBytes(kSecRandomDefault, v.len() as size_t, v.as_mut_ptr())
            };
            if ret == -1 {
                panic!("couldn't generate random bytes: {}", os::last_os_error());
            }
        }
    }
}

#[cfg(windows)]
mod imp {
    extern crate libc;

    use io::{IoResult, IoError};
    use mem;
    use ops::Drop;
    use os;
    use rand::Rng;
    use result::Result::{Ok, Err};
    use self::libc::{DWORD, BYTE, LPCSTR, BOOL};
    use self::libc::types::os::arch::extra::{LONG_PTR};
    use slice::{SlicePrelude};

    type HCRYPTPROV = LONG_PTR;

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
    /// This does not block.
    pub struct OsRng {
        hcryptprov: HCRYPTPROV
    }

    static PROV_RSA_FULL: DWORD = 1;
    static CRYPT_SILENT: DWORD = 64;
    static CRYPT_VERIFYCONTEXT: DWORD = 0xF0000000;

    #[allow(non_snake_case)]
    extern "system" {
        fn CryptAcquireContextA(phProv: *mut HCRYPTPROV,
                                pszContainer: LPCSTR,
                                pszProvider: LPCSTR,
                                dwProvType: DWORD,
                                dwFlags: DWORD) -> BOOL;
        fn CryptGenRandom(hProv: HCRYPTPROV,
                          dwLen: DWORD,
                          pbBuffer: *mut BYTE) -> BOOL;
        fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) -> BOOL;
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            let mut hcp = 0;
            let ret = unsafe {
                CryptAcquireContextA(&mut hcp, 0 as LPCSTR, 0 as LPCSTR,
                                     PROV_RSA_FULL,
                                     CRYPT_VERIFYCONTEXT | CRYPT_SILENT)
            };

            if ret == 0 {
                Err(IoError::last_error())
            } else {
                Ok(OsRng { hcryptprov: hcp })
            }
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            let mut v = [0u8, .. 4];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn next_u64(&mut self) -> u64 {
            let mut v = [0u8, .. 8];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            let ret = unsafe {
                CryptGenRandom(self.hcryptprov, v.len() as DWORD,
                               v.as_mut_ptr())
            };
            if ret == 0 {
                panic!("couldn't generate random bytes: {}", os::last_os_error());
            }
        }
    }

    impl Drop for OsRng {
        fn drop(&mut self) {
            let ret = unsafe {
                CryptReleaseContext(self.hcryptprov, 0)
            };
            if ret == 0 {
                panic!("couldn't release context: {}", os::last_os_error());
            }
        }
    }
}

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

    use super::OsRng;
    use rand::Rng;
    use task;

    #[test]
    fn test_os_rng() {
        let mut r = OsRng::new().unwrap();

        r.next_u32();
        r.next_u64();

        let mut v = [0u8, .. 1000];
        r.fill_bytes(&mut v);
    }

    #[test]
    fn test_os_rng_tasks() {

        let mut txs = vec!();
        for _ in range(0u, 20) {
            let (tx, rx) = channel();
            txs.push(tx);
            task::spawn(move|| {
                // wait until all the tasks are ready to go.
                rx.recv();

                // deschedule to attempt to interleave things as much
                // as possible (XXX: is this a good test?)
                let mut r = OsRng::new().unwrap();
                task::deschedule();
                let mut v = [0u8, .. 1000];

                for _ in range(0u, 100) {
                    r.next_u32();
                    task::deschedule();
                    r.next_u64();
                    task::deschedule();
                    r.fill_bytes(&mut v);
                    task::deschedule();
                }
            })
        }

        // start all the tasks
        for tx in txs.iter() {
            tx.send(())
        }
    }
}
<commit_msg>Fix Markdown syntax in docs for OsRng<commit_after>// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Interfaces to the operating system provided random number
//! generators.

pub use self::imp::OsRng;

#[cfg(all(unix, not(target_os = "ios")))]
mod imp {
    extern crate libc;

    use self::OsRngInner::*;

    use io::{IoResult, File};
    use path::Path;
    use rand::Rng;
    use rand::reader::ReaderRng;
    use result::Result::{Ok, Err};
    use slice::SlicePrelude;
    use mem;
    use os::errno;

    #[cfg(all(target_os = "linux",
              any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
    fn getrandom(buf: &mut [u8]) -> libc::c_long {
        extern "C" {
            fn syscall(number: libc::c_long, ...) -> libc::c_long;
        }

        #[cfg(target_arch = "x86_64")]
        const NR_GETRANDOM: libc::c_long = 318;
        #[cfg(target_arch = "x86")]
        const NR_GETRANDOM: libc::c_long = 355;
        #[cfg(target_arch = "arm")]
        const NR_GETRANDOM: libc::c_long = 384;

        unsafe {
            syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0u)
        }
    }

    #[cfg(not(all(target_os = "linux",
                  any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
    fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }

    fn getrandom_fill_bytes(v: &mut [u8]) {
        let mut read = 0;
        let len = v.len();
        while read < len {
            let result = getrandom(v[mut read..]);
            if result == -1 {
                let err = errno() as libc::c_int;
                if err == libc::EINTR {
                    continue;
                } else {
                    panic!("unexpected getrandom error: {}", err);
                }
            } else {
                read += result as uint;
            }
        }
    }

    fn getrandom_next_u32() -> u32 {
        let mut buf: [u8, ..4] = [0u8, ..4];
        getrandom_fill_bytes(&mut buf);
        unsafe { mem::transmute::<[u8, ..4], u32>(buf) }
    }

    fn getrandom_next_u64() -> u64 {
        let mut buf: [u8, ..8] = [0u8, ..8];
        getrandom_fill_bytes(&mut buf);
        unsafe { mem::transmute::<[u8, ..8], u64>(buf) }
    }

    #[cfg(all(target_os = "linux",
              any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
    fn is_getrandom_available() -> bool {
        use sync::atomic::{AtomicBool, INIT_ATOMIC_BOOL, Relaxed};

        static GETRANDOM_CHECKED: AtomicBool = INIT_ATOMIC_BOOL;
        static GETRANDOM_AVAILABLE: AtomicBool = INIT_ATOMIC_BOOL;

        if !GETRANDOM_CHECKED.load(Relaxed) {
            let mut buf: [u8, ..0] = [];
            let result = getrandom(&mut buf);
            let available = if result == -1 {
                let err = errno() as libc::c_int;
                err != libc::ENOSYS
            } else {
                true
            };
            GETRANDOM_AVAILABLE.store(available, Relaxed);
            GETRANDOM_CHECKED.store(true, Relaxed);
            available
        } else {
            GETRANDOM_AVAILABLE.load(Relaxed)
        }
    }

    #[cfg(not(all(target_os = "linux",
                  any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
    fn is_getrandom_available() -> bool { false }

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed.
    ///
    /// This does not block.
    pub struct OsRng {
        inner: OsRngInner,
    }

    enum OsRngInner {
        OsGetrandomRng,
        OsReaderRng(ReaderRng<File>),
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            if is_getrandom_available() {
                return Ok(OsRng { inner: OsGetrandomRng });
            }

            let reader = try!(File::open(&Path::new("/dev/urandom")));
            let reader_rng = ReaderRng::new(reader);

            Ok(OsRng { inner: OsReaderRng(reader_rng) })
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            match self.inner {
                OsGetrandomRng => getrandom_next_u32(),
                OsReaderRng(ref mut rng) => rng.next_u32(),
            }
        }
        fn next_u64(&mut self) -> u64 {
            match self.inner {
                OsGetrandomRng => getrandom_next_u64(),
                OsReaderRng(ref mut rng) => rng.next_u64(),
            }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            match self.inner {
                OsGetrandomRng => getrandom_fill_bytes(v),
                OsReaderRng(ref mut rng) => rng.fill_bytes(v)
            }
        }
    }
}

#[cfg(target_os = "ios")]
mod imp {
    extern crate libc;

    use io::{IoResult};
    use kinds::marker;
    use mem;
    use os;
    use rand::Rng;
    use result::Result::{Ok};
    use self::libc::{c_int, size_t};
    use slice::{SlicePrelude};

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed.
    ///
    /// This does not block.
    pub struct OsRng {
        marker: marker::NoCopy
    }

    #[repr(C)]
    struct SecRandom;

    #[allow(non_upper_case_globals)]
    static kSecRandomDefault: *const SecRandom = 0 as *const SecRandom;

    #[link(name = "Security", kind = "framework")]
    extern "C" {
        fn SecRandomCopyBytes(rnd: *const SecRandom,
                              count: size_t, bytes: *mut u8) -> c_int;
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            Ok(OsRng {marker: marker::NoCopy} )
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            let mut v = [0u8, .. 4];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn next_u64(&mut self) -> u64 {
            let mut v = [0u8, .. 8];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            let ret = unsafe {
                SecRandomCopyBytes(kSecRandomDefault, v.len() as size_t, v.as_mut_ptr())
            };
            if ret == -1 {
                panic!("couldn't generate random bytes: {}", os::last_os_error());
            }
        }
    }
}

#[cfg(windows)]
mod imp {
    extern crate libc;

    use io::{IoResult, IoError};
    use mem;
    use ops::Drop;
    use os;
    use rand::Rng;
    use result::Result::{Ok, Err};
    use self::libc::{DWORD, BYTE, LPCSTR, BOOL};
    use self::libc::types::os::arch::extra::{LONG_PTR};
    use slice::{SlicePrelude};

    type HCRYPTPROV = LONG_PTR;

    /// A random number generator that retrieves randomness straight from
    /// the operating system. Platform sources:
    ///
    /// - Unix-like systems (Linux, Android, Mac OSX): read directly from
    ///   `/dev/urandom`, or from `getrandom(2)` system call if available.
    /// - Windows: calls `CryptGenRandom`, using the default cryptographic
    ///   service provider with the `PROV_RSA_FULL` type.
    /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed.
    ///
    /// This does not block.
    pub struct OsRng {
        hcryptprov: HCRYPTPROV
    }

    static PROV_RSA_FULL: DWORD = 1;
    static CRYPT_SILENT: DWORD = 64;
    static CRYPT_VERIFYCONTEXT: DWORD = 0xF0000000;

    #[allow(non_snake_case)]
    extern "system" {
        fn CryptAcquireContextA(phProv: *mut HCRYPTPROV,
                                pszContainer: LPCSTR,
                                pszProvider: LPCSTR,
                                dwProvType: DWORD,
                                dwFlags: DWORD) -> BOOL;
        fn CryptGenRandom(hProv: HCRYPTPROV,
                          dwLen: DWORD,
                          pbBuffer: *mut BYTE) -> BOOL;
        fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) -> BOOL;
    }

    impl OsRng {
        /// Create a new `OsRng`.
        pub fn new() -> IoResult<OsRng> {
            let mut hcp = 0;
            let ret = unsafe {
                CryptAcquireContextA(&mut hcp, 0 as LPCSTR, 0 as LPCSTR,
                                     PROV_RSA_FULL,
                                     CRYPT_VERIFYCONTEXT | CRYPT_SILENT)
            };

            if ret == 0 {
                Err(IoError::last_error())
            } else {
                Ok(OsRng { hcryptprov: hcp })
            }
        }
    }

    impl Rng for OsRng {
        fn next_u32(&mut self) -> u32 {
            let mut v = [0u8, .. 4];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn next_u64(&mut self) -> u64 {
            let mut v = [0u8, .. 8];
            self.fill_bytes(&mut v);
            unsafe { mem::transmute(v) }
        }
        fn fill_bytes(&mut self, v: &mut [u8]) {
            let ret = unsafe {
                CryptGenRandom(self.hcryptprov, v.len() as DWORD,
                               v.as_mut_ptr())
            };
            if ret == 0 {
                panic!("couldn't generate random bytes: {}", os::last_os_error());
            }
        }
    }

    impl Drop for OsRng {
        fn drop(&mut self) {
            let ret = unsafe {
                CryptReleaseContext(self.hcryptprov, 0)
            };
            if ret == 0 {
                panic!("couldn't release context: {}", os::last_os_error());
            }
        }
    }
}

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

    use super::OsRng;
    use rand::Rng;
    use task;

    #[test]
    fn test_os_rng() {
        let mut r = OsRng::new().unwrap();

        r.next_u32();
        r.next_u64();

        let mut v = [0u8, .. 1000];
        r.fill_bytes(&mut v);
    }

    #[test]
    fn test_os_rng_tasks() {

        let mut txs = vec!();
        for _ in range(0u, 20) {
            let (tx, rx) = channel();
            txs.push(tx);
            task::spawn(move|| {
                // wait until all the tasks are ready to go.
                rx.recv();

                // deschedule to attempt to interleave things as much
                // as possible (XXX: is this a good test?)
                let mut r = OsRng::new().unwrap();
                task::deschedule();
                let mut v = [0u8, .. 1000];

                for _ in range(0u, 100) {
                    r.next_u32();
                    task::deschedule();
                    r.next_u64();
                    task::deschedule();
                    r.fill_bytes(&mut v);
                    task::deschedule();
                }
            })
        }

        // start all the tasks
        for tx in txs.iter() {
            tx.send(())
        }
    }
}
<|endoftext|>


<commit_before>use std::fs;
use std::env;
use std::path::Path;
use std::process::Command;
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;

use clap::{App, ArgMatches};
use hyper::client::{Client, RedirectPolicy};
use hyper::client::request::Request;
use hyper::header::{UserAgent, ContentLength};
use hyper::method::Method;
use url::Url;
use serde_json;

use utils;
use CliResult;
use commands::Config;

#[derive(Debug, Serialize, Deserialize)]
struct Asset {
    browser_download_url: String,
    name: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Release {
    tag_name: String,
    assets: Vec<Asset>,
}

struct ReleaseInfo {
    version: String,
    download_url: Url,
}

fn get_asset_name() -> CliResult<String> {
    let p = Command::new("uname").arg("-sm").output()?;
    let output = String::from_utf8(p.stdout)?;
    let mut iter = output.trim().split(' ');
    let platform = iter.next().unwrap_or("unknown");
    let arch = iter.next().unwrap_or("unknown");
    Ok(format!("sentry-cli-{}-{}", platform, arch))
}

fn get_latest_release() -> CliResult<ReleaseInfo> {
    let ref_name = get_asset_name()?;

    let mut req = Request::new(Method::Get,
        Url::parse("https://api.github.com/repos/getsentry/sentry-cli/releases/latest")?)?;
    {
        let mut headers = req.headers_mut();
        headers.set(UserAgent("sentry-cli".into()));
    }
    let mut resp = req.start()?.send()?;
    if !resp.status.is_success() {
        fail!(resp);
    }

    let rv : Release = serde_json::from_reader(&mut resp)?;

    for asset in rv.assets {
        if asset.name == ref_name {
            return Ok(ReleaseInfo {
                version: rv.tag_name,
                download_url: Url::parse(&asset.browser_download_url)?
            });
        }
    }

    fail!("Could not find download URL for updates.");
}

fn download_url<P: AsRef<Path>>(url: &Url, dst: P) -> CliResult<()> {
    let mut client = Client::new();
    client.set_redirect_policy(RedirectPolicy::FollowAll);
    let mut resp = client.get(url.to_owned()).send()?;
    if !resp.status.is_success() {
        fail!(resp);
    }
    let content_length = match resp.headers.get::<ContentLength>() {
        Some(&ContentLength(length)) => length,
        None => 0
    };

    print!("Downloading...");
    let mut last_status = 0;
    let mut buffer = [0; 4096];
    let mut downloaded = 0;
    let mut f = fs::File::create(dst)?;
    loop {
        let chunk_size = resp.read(&mut buffer)?;
        if chunk_size == 0 {
            break;
        } else {
            f.write(&buffer[..chunk_size])?;
            downloaded += chunk_size as u64;
        }
        if content_length > 0 {
            let status = downloaded * 100 / content_length;
            if status != last_status {
                print!("\rDownloading... {}%", status);
                last_status = status;
            }
        }
    }
    println!("");
    println!("Done!");
    Ok(())
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b>
{
    app
        .about("update the sentry-cli executable")
}

pub fn execute<'a>(_matches: &ArgMatches<'a>, _config: &Config) -> CliResult<()> {
    let exe = env::current_exe()?;
    let need_sudo = !utils::is_writable(&exe);
    let latest_release = get_latest_release()?;
    let tmp_path = if need_sudo {
        env::temp_dir().join(".sentry-cli.part")
    } else {
        exe.parent().unwrap().join(".sentry-cli.part")
    };
    let current_version = env!("CARGO_PKG_VERSION");
    println!("Latest release is {}", latest_release.version);

    if latest_release.version == current_version {
        println!("Already up to date!");
        return Ok(());
    }

    println!("Updating executable at {}", exe.display());

    match download_url(&latest_release.download_url, &tmp_path) {
        Err(err) => {
            fs::remove_file(tmp_path).ok();
            return Err(err);
        },
        Ok(()) => {},
    }

    let mut perm = fs::metadata(&tmp_path)?.permissions();
    perm.set_mode(0o755);
    fs::set_permissions(&tmp_path, perm)?;

    if need_sudo {
        println!("Need to sudo to overwrite {}", exe.display());
        Command::new("sudo")
            .arg("-k")
            .arg("mv")
            .arg(&tmp_path)
            .arg(&exe)
            .status()?;
    } else {
        fs::rename(&tmp_path, &exe)?;
    }
    println!("Updated!");

    Ok(())
}
<commit_msg>Move a line of code<commit_after>use std::fs;
use std::env;
use std::path::Path;
use std::process::Command;
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;

use clap::{App, ArgMatches};
use hyper::client::{Client, RedirectPolicy};
use hyper::client::request::Request;
use hyper::header::{UserAgent, ContentLength};
use hyper::method::Method;
use url::Url;
use serde_json;

use utils;
use CliResult;
use commands::Config;

#[derive(Debug, Serialize, Deserialize)]
struct Asset {
    browser_download_url: String,
    name: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Release {
    tag_name: String,
    assets: Vec<Asset>,
}

struct ReleaseInfo {
    version: String,
    download_url: Url,
}

fn get_asset_name() -> CliResult<String> {
    let p = Command::new("uname").arg("-sm").output()?;
    let output = String::from_utf8(p.stdout)?;
    let mut iter = output.trim().split(' ');
    let platform = iter.next().unwrap_or("unknown");
    let arch = iter.next().unwrap_or("unknown");
    Ok(format!("sentry-cli-{}-{}", platform, arch))
}

fn get_latest_release() -> CliResult<ReleaseInfo> {
    let ref_name = get_asset_name()?;

    let mut req = Request::new(Method::Get,
        Url::parse("https://api.github.com/repos/getsentry/sentry-cli/releases/latest")?)?;
    {
        let mut headers = req.headers_mut();
        headers.set(UserAgent("sentry-cli".into()));
    }
    let mut resp = req.start()?.send()?;
    if !resp.status.is_success() {
        fail!(resp);
    }

    let rv : Release = serde_json::from_reader(&mut resp)?;

    for asset in rv.assets {
        if asset.name == ref_name {
            return Ok(ReleaseInfo {
                version: rv.tag_name,
                download_url: Url::parse(&asset.browser_download_url)?
            });
        }
    }

    fail!("Could not find download URL for updates.");
}

fn download_url<P: AsRef<Path>>(url: &Url, dst: P) -> CliResult<()> {
    let mut client = Client::new();
    client.set_redirect_policy(RedirectPolicy::FollowAll);
    let mut resp = client.get(url.to_owned()).send()?;
    if !resp.status.is_success() {
        fail!(resp);
    }
    let content_length = match resp.headers.get::<ContentLength>() {
        Some(&ContentLength(length)) => length,
        None => 0
    };

    print!("Downloading...");
    let mut last_status = 0;
    let mut buffer = [0; 4096];
    let mut downloaded = 0;
    let mut f = fs::File::create(dst)?;
    loop {
        let chunk_size = resp.read(&mut buffer)?;
        if chunk_size == 0 {
            break;
        } else {
            f.write(&buffer[..chunk_size])?;
            downloaded += chunk_size as u64;
        }
        if content_length > 0 {
            let status = downloaded * 100 / content_length;
            if status != last_status {
                print!("\rDownloading... {}%", status);
                last_status = status;
            }
        }
    }
    println!("");
    println!("Done!");
    Ok(())
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b>
{
    app
        .about("update the sentry-cli executable")
}

pub fn execute<'a>(_matches: &ArgMatches<'a>, _config: &Config) -> CliResult<()> {
    let current_version = env!("CARGO_PKG_VERSION");
    let exe = env::current_exe()?;
    let need_sudo = !utils::is_writable(&exe);
    let latest_release = get_latest_release()?;
    let tmp_path = if need_sudo {
        env::temp_dir().join(".sentry-cli.part")
    } else {
        exe.parent().unwrap().join(".sentry-cli.part")
    };

    println!("Latest release is {}", latest_release.version);
    if latest_release.version == current_version {
        println!("Already up to date!");
        return Ok(());
    }

    println!("Updating executable at {}", exe.display());

    match download_url(&latest_release.download_url, &tmp_path) {
        Err(err) => {
            fs::remove_file(tmp_path).ok();
            return Err(err);
        },
        Ok(()) => {},
    }

    let mut perm = fs::metadata(&tmp_path)?.permissions();
    perm.set_mode(0o755);
    fs::set_permissions(&tmp_path, perm)?;

    if need_sudo {
        println!("Need to sudo to overwrite {}", exe.display());
        Command::new("sudo")
            .arg("-k")
            .arg("mv")
            .arg(&tmp_path)
            .arg(&exe)
            .status()?;
    } else {
        fs::rename(&tmp_path, &exe)?;
    }
    println!("Updated!");

    Ok(())
}
<|endoftext|>


<commit_before>use std::{env, ffi::OsString, fmt, net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::bail;
use clap::{Arg, ArgGroup, Command as ClapCommand};
use sccache::{config, dist::ServerId};
use syslog::Facility;

use crate::cmdline::{AuthSubcommand, Command};

#[derive(Debug)]
struct TokenLength(usize);

impl TokenLength {
    fn as_bytes(&self) -> usize {
        self.0 / 8
    }

    fn from_bits(bits: &str) -> anyhow::Result<Self> {
        let bits: usize = bits.parse()?;
        if bits % 8 != 0 || bits < 64 || bits > 4_096 {
            bail!("Number of bits must be divisible by 8, greater than 64 and less than 4096")
        }

        Ok(Self(bits))
    }
}

impl fmt::Display for TokenLength {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Clone, Copy)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl LogLevel {
    fn as_str(&self) -> &'static str {
        match self {
            Self::Error => "error",
            Self::Warn => "warn",
            Self::Info => "info",
            Self::Debug => "debug",
            Self::Trace => "trace",
        }
    }

    fn values() -> &'static [Self] {
        &[
            Self::Error,
            Self::Warn,
            Self::Info,
            Self::Debug,
            Self::Trace,
        ]
    }
}

impl FromStr for LogLevel {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let variant = match s {
            "error" => Self::Error,
            "warn" => Self::Warn,
            "info" => Self::Info,
            "debug" => Self::Debug,
            "trace" => Self::Trace,
            _ => bail!("Unknown log level: {:?}", s),
        };

        Ok(variant)
    }
}

impl From<LogLevel> for log::LevelFilter {
    fn from(log_level: LogLevel) -> Self {
        match log_level {
            LogLevel::Error => Self::Error,
            LogLevel::Warn => Self::Warn,
            LogLevel::Info => Self::Info,
            LogLevel::Debug => Self::Debug,
            LogLevel::Trace => Self::Trace,
        }
    }
}

/// Parse the commandline into a `Command` to execute.
pub fn parse() -> Command {
    match try_parse_from(env::args()) {
        Ok(cmd) => cmd,
        Err(e) => match e.downcast::<clap::error::Error>() {
            Ok(clap_err) => clap_err.exit(),
            Err(some_other_err) => {
                println!("sccache-dist: {some_other_err}");
                for source_err in some_other_err.chain().skip(1) {
                    println!("sccache-dist: caused by: {source_err}");
                }
                std::process::exit(1);
            }
        },
    }
}

fn flag_infer_long(name: &'static str) -> Arg<'static> {
    Arg::new(name).long(name)
}

fn get_clap_command() -> ClapCommand<'static> {
    let syslog = flag_infer_long("syslog")
        .help("Log to the syslog with LEVEL")
        .value_name("LEVEL")
        .possible_values(LogLevel::values().iter().map(LogLevel::as_str));
    let config_with_help_message =
        |help: &'static str| flag_infer_long("config").help(help).value_name("PATH");

    ClapCommand::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .subcommand_required(true)
        .subcommand(
            ClapCommand::new("auth")
                .subcommand_required(true)
                .subcommand(ClapCommand::new("generate-jwt-hs256-key"))
                .subcommand(
                    ClapCommand::new("generate-jwt-hs256-server-token")
                        .args(&[
                            flag_infer_long("server")
                                .help("Generate a key for the specified server")
                                .value_name("SERVER_ADDR")
                                .required(true),
                            flag_infer_long("secret-key")
                                .help("Use specified key to create the token")
                                .value_name("KEY"),
                            config_with_help_message(
                                "Use the key from the scheduler config file at PATH",
                            ),
                        ])
                        .group(
                            ArgGroup::new("key_source_mutual_exclusion")
                                .args(&["config", "secret-key"])
                                .required(true),
                        ),
                )
                .subcommand(
                    ClapCommand::new("generate-shared-token").arg(
                        flag_infer_long("bits")
                            .help("Use the specified number of bits of randomness")
                            .value_name("BITS")
                            .default_value("256")
                            .validator(TokenLength::from_bits),
                    ),
                ),
        )
        .subcommand(ClapCommand::new("scheduler").args(&[
            config_with_help_message("Use the scheduler config file at PATH").required(true),
            syslog.clone(),
        ]))
        .subcommand(ClapCommand::new("server").args(&[
            config_with_help_message("Use the server config file at PATH").required(true),
            syslog,
        ]))
}

fn check_init_syslog(name: &str, log_level: LogLevel) {
    let level = log::LevelFilter::from(log_level);
    drop(syslog::init(Facility::LOG_DAEMON, level, Some(name)));
}

fn try_parse_from(
    args: impl IntoIterator<Item = impl Into<OsString> + Clone>,
) -> anyhow::Result<Command> {
    let matches = get_clap_command().try_get_matches_from(args)?;

    Ok(match matches.subcommand() {
        Some(("auth", matches)) => Command::Auth(match matches.subcommand() {
            // Size based on https://briansmith.org/rustdoc/ring/hmac/fn.recommended_key_len.html
            Some(("generate-jwt-hs256-key", _)) => AuthSubcommand::Base64 { num_bytes: 256 / 8 },
            Some(("generate-jwt-hs256-server-token", matches)) => {
                let server_addr: SocketAddr = matches.value_of_t("server")?;
                let server_id = ServerId::new(server_addr);

                let secret_key = if matches.is_present("config") {
                    let config_path: PathBuf = matches.value_of_t("config")?;
                    if let Some(config) = config::scheduler::from_path(&config_path)? {
                        match config.server_auth {
                            config::scheduler::ServerAuth::JwtHS256 { secret_key } => secret_key,
                            config::scheduler::ServerAuth::Insecure
                            | config::scheduler::ServerAuth::Token { .. } => {
                                bail!("Scheduler not configured with JWT HS256")
                            }
                        }
                    } else {
                        bail!("Could not load config")
                    }
                } else {
                    matches.value_of_t("secret-key")?
                };

                AuthSubcommand::JwtHS256ServerToken {
                    secret_key,
                    server_id,
                }
            }
            Some(("generate-shared-token", matches)) => {
                let token_bits = TokenLength::from_bits(
                    matches.value_of("bits").expect("clap provides default"),
                )
                .expect("clap uses `from_bits` as a validator");

                AuthSubcommand::Base64 {
                    num_bytes: token_bits.as_bytes(),
                }
            }
            _ => unreachable!("Subcommand is enforced by clap"),
        }),
        Some(("scheduler", matches)) => {
            if matches.is_present("syslog") {
                let log_level: LogLevel = matches.value_of_t("syslog")?;
                check_init_syslog("sccache-scheduler", log_level);
            }

            let config_path: PathBuf = matches.value_of_t("config")?;
            if let Some(config) = config::scheduler::from_path(&config_path)? {
                Command::Scheduler(config)
            } else {
                bail!("Could not load config")
            }
        }
        Some(("server", matches)) => {
            if matches.is_present("syslog") {
                let log_level: LogLevel = matches.value_of_t("syslog")?;
                check_init_syslog("sccache-buildserver", log_level);
            }

            let config_path: PathBuf = matches.value_of_t("config")?;
            if let Some(config) = config::server::from_path(&config_path)? {
                Command::Server(config)
            } else {
                bail!("Could not load config")
            }
        }
        _ => unreachable!("Subcommand is enforced by clap"),
    })
}

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

    const EXE: &str = "sccache-dist";

    fn auth_generate_shared_tokens_bits(bit_val: &'static str) -> Vec<&'static str> {
        vec![EXE, "auth", "generate-shared-token", "--bits", bit_val]
    }

    fn auth_generate_jwt_hs256_server_token(subcommand_args: &[&'static str]) -> Vec<&'static str> {
        let mut args = vec![EXE, "auth", "generate-jwt-hs256-server-token"];
        args.extend_from_slice(subcommand_args);
        args
    }

    #[test]
    fn debug_assert() {
        get_clap_command().debug_assert()
    }

    #[test]
    fn missing_required_subcommands_fails() {
        let args_sets = &[vec![EXE], vec![EXE, "auth"]];

        for args in args_sets {
            assert!(try_parse_from(args).is_err());
        }
    }

    #[test]
    fn invalid_token_bits_fails() {
        let invalid_vals = vec!["not_a_num", "58", "8000", "70"];

        for invalid_val in invalid_vals {
            let args = auth_generate_shared_tokens_bits(invalid_val);
            assert!(try_parse_from(args).is_err());
        }
    }

    #[test]
    fn auth_generate_server_token_needs_key_source() {
        let server_args = &["--server", "127.0.0.1:4321"];

        let no_key = auth_generate_jwt_hs256_server_token(server_args);
        assert!(try_parse_from(no_key).is_err());

        let mut too_many_keys = auth_generate_jwt_hs256_server_token(server_args);
        too_many_keys.extend_from_slice(&["--secret-key", "secret", "--config", "some/path.toml"]);
        assert!(try_parse_from(too_many_keys).is_err());
    }

    // This is all just to work around `PartialEq` not being on some of the values used in variants
    // for `Command` yet
    fn assert_args_parse_to_auth(args: Vec<&'static str>, ideal_auth: AuthSubcommand) {
        match try_parse_from(&args) {
            Ok(Command::Auth(auth)) => assert_eq!(auth, ideal_auth),
            _ => panic!("Bad parsing for: {:#?}", args),
        }
    }

    #[test]
    fn auth_generate_jwt_hs256_key_good() {
        let args = vec![EXE, "auth", "generate-jwt-hs256-key"];

        assert_args_parse_to_auth(args, AuthSubcommand::Base64 { num_bytes: 256 / 8 });
    }

    #[test]
    fn auth_generate_jwt_hs256_server_token_good() {
        let base = auth_generate_jwt_hs256_server_token(&["--server", "127.0.0.1:4321"]);
        let server_socket: SocketAddr = "127.0.0.1:4321".parse().unwrap();
        let server_id = ServerId::new(server_socket);

        let mut secret_key = base.clone();
        secret_key.extend_from_slice(&["--secret-key", "very secret"]);
        assert_args_parse_to_auth(
            secret_key,
            AuthSubcommand::JwtHS256ServerToken {
                server_id,
                secret_key: "very secret".to_owned(),
            },
        );

        // TODO: need some sample config to be used here
        // let mut config = base;
        // config.extend_from_slice(&["--config", "some/path.toml"]);
    }

    #[test]
    fn auth_generate_shared_token_good() {
        let raw_to_expected_bit_vals = &[
            ("64", 64 / 8),
            ("128", 128 / 8),
            ("136", 136 / 8),
            ("4000", 4_000 / 8),
        ];

        for (raw, expected) in raw_to_expected_bit_vals {
            let args = auth_generate_shared_tokens_bits(raw);
            assert_args_parse_to_auth(
                args,
                AuthSubcommand::Base64 {
                    num_bytes: *expected,
                },
            );
        }
    }

    // TODO: add tests for with sample config file
    // #[test]
    // fn scheduler_good() {
    //     todo!()
    // }

    // TODO: add tests for with sample config file
    // #[test]
    // fn server_good() {
    //     todo!()
    // }
}
<commit_msg>Remove lingering todos in favor of gh issues<commit_after>use std::{env, ffi::OsString, fmt, net::SocketAddr, path::PathBuf, str::FromStr};

use anyhow::bail;
use clap::{Arg, ArgGroup, Command as ClapCommand};
use sccache::{config, dist::ServerId};
use syslog::Facility;

use crate::cmdline::{AuthSubcommand, Command};

#[derive(Debug)]
struct TokenLength(usize);

impl TokenLength {
    fn as_bytes(&self) -> usize {
        self.0 / 8
    }

    fn from_bits(bits: &str) -> anyhow::Result<Self> {
        let bits: usize = bits.parse()?;
        if bits % 8 != 0 || bits < 64 || bits > 4_096 {
            bail!("Number of bits must be divisible by 8, greater than 64 and less than 4096")
        }

        Ok(Self(bits))
    }
}

impl fmt::Display for TokenLength {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Clone, Copy)]
enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl LogLevel {
    fn as_str(&self) -> &'static str {
        match self {
            Self::Error => "error",
            Self::Warn => "warn",
            Self::Info => "info",
            Self::Debug => "debug",
            Self::Trace => "trace",
        }
    }

    fn values() -> &'static [Self] {
        &[
            Self::Error,
            Self::Warn,
            Self::Info,
            Self::Debug,
            Self::Trace,
        ]
    }
}

impl FromStr for LogLevel {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let variant = match s {
            "error" => Self::Error,
            "warn" => Self::Warn,
            "info" => Self::Info,
            "debug" => Self::Debug,
            "trace" => Self::Trace,
            _ => bail!("Unknown log level: {:?}", s),
        };

        Ok(variant)
    }
}

impl From<LogLevel> for log::LevelFilter {
    fn from(log_level: LogLevel) -> Self {
        match log_level {
            LogLevel::Error => Self::Error,
            LogLevel::Warn => Self::Warn,
            LogLevel::Info => Self::Info,
            LogLevel::Debug => Self::Debug,
            LogLevel::Trace => Self::Trace,
        }
    }
}

/// Parse the commandline into a `Command` to execute.
pub fn parse() -> Command {
    match try_parse_from(env::args()) {
        Ok(cmd) => cmd,
        Err(e) => match e.downcast::<clap::error::Error>() {
            Ok(clap_err) => clap_err.exit(),
            Err(some_other_err) => {
                println!("sccache-dist: {some_other_err}");
                for source_err in some_other_err.chain().skip(1) {
                    println!("sccache-dist: caused by: {source_err}");
                }
                std::process::exit(1);
            }
        },
    }
}

fn flag_infer_long(name: &'static str) -> Arg<'static> {
    Arg::new(name).long(name)
}

fn get_clap_command() -> ClapCommand<'static> {
    let syslog = flag_infer_long("syslog")
        .help("Log to the syslog with LEVEL")
        .value_name("LEVEL")
        .possible_values(LogLevel::values().iter().map(LogLevel::as_str));
    let config_with_help_message =
        |help: &'static str| flag_infer_long("config").help(help).value_name("PATH");

    ClapCommand::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .subcommand_required(true)
        .subcommand(
            ClapCommand::new("auth")
                .subcommand_required(true)
                .subcommand(ClapCommand::new("generate-jwt-hs256-key"))
                .subcommand(
                    ClapCommand::new("generate-jwt-hs256-server-token")
                        .args(&[
                            flag_infer_long("server")
                                .help("Generate a key for the specified server")
                                .value_name("SERVER_ADDR")
                                .required(true),
                            flag_infer_long("secret-key")
                                .help("Use specified key to create the token")
                                .value_name("KEY"),
                            config_with_help_message(
                                "Use the key from the scheduler config file at PATH",
                            ),
                        ])
                        .group(
                            ArgGroup::new("key_source_mutual_exclusion")
                                .args(&["config", "secret-key"])
                                .required(true),
                        ),
                )
                .subcommand(
                    ClapCommand::new("generate-shared-token").arg(
                        flag_infer_long("bits")
                            .help("Use the specified number of bits of randomness")
                            .value_name("BITS")
                            .default_value("256")
                            .validator(TokenLength::from_bits),
                    ),
                ),
        )
        .subcommand(ClapCommand::new("scheduler").args(&[
            config_with_help_message("Use the scheduler config file at PATH").required(true),
            syslog.clone(),
        ]))
        .subcommand(ClapCommand::new("server").args(&[
            config_with_help_message("Use the server config file at PATH").required(true),
            syslog,
        ]))
}

fn check_init_syslog(name: &str, log_level: LogLevel) {
    let level = log::LevelFilter::from(log_level);
    drop(syslog::init(Facility::LOG_DAEMON, level, Some(name)));
}

fn try_parse_from(
    args: impl IntoIterator<Item = impl Into<OsString> + Clone>,
) -> anyhow::Result<Command> {
    let matches = get_clap_command().try_get_matches_from(args)?;

    Ok(match matches.subcommand() {
        Some(("auth", matches)) => Command::Auth(match matches.subcommand() {
            // Size based on https://briansmith.org/rustdoc/ring/hmac/fn.recommended_key_len.html
            Some(("generate-jwt-hs256-key", _)) => AuthSubcommand::Base64 { num_bytes: 256 / 8 },
            Some(("generate-jwt-hs256-server-token", matches)) => {
                let server_addr: SocketAddr = matches.value_of_t("server")?;
                let server_id = ServerId::new(server_addr);

                let secret_key = if matches.is_present("config") {
                    let config_path: PathBuf = matches.value_of_t("config")?;
                    if let Some(config) = config::scheduler::from_path(&config_path)? {
                        match config.server_auth {
                            config::scheduler::ServerAuth::JwtHS256 { secret_key } => secret_key,
                            config::scheduler::ServerAuth::Insecure
                            | config::scheduler::ServerAuth::Token { .. } => {
                                bail!("Scheduler not configured with JWT HS256")
                            }
                        }
                    } else {
                        bail!("Could not load config")
                    }
                } else {
                    matches.value_of_t("secret-key")?
                };

                AuthSubcommand::JwtHS256ServerToken {
                    secret_key,
                    server_id,
                }
            }
            Some(("generate-shared-token", matches)) => {
                let token_bits = TokenLength::from_bits(
                    matches.value_of("bits").expect("clap provides default"),
                )
                .expect("clap uses `from_bits` as a validator");

                AuthSubcommand::Base64 {
                    num_bytes: token_bits.as_bytes(),
                }
            }
            _ => unreachable!("Subcommand is enforced by clap"),
        }),
        Some(("scheduler", matches)) => {
            if matches.is_present("syslog") {
                let log_level: LogLevel = matches.value_of_t("syslog")?;
                check_init_syslog("sccache-scheduler", log_level);
            }

            let config_path: PathBuf = matches.value_of_t("config")?;
            if let Some(config) = config::scheduler::from_path(&config_path)? {
                Command::Scheduler(config)
            } else {
                bail!("Could not load config")
            }
        }
        Some(("server", matches)) => {
            if matches.is_present("syslog") {
                let log_level: LogLevel = matches.value_of_t("syslog")?;
                check_init_syslog("sccache-buildserver", log_level);
            }

            let config_path: PathBuf = matches.value_of_t("config")?;
            if let Some(config) = config::server::from_path(&config_path)? {
                Command::Server(config)
            } else {
                bail!("Could not load config")
            }
        }
        _ => unreachable!("Subcommand is enforced by clap"),
    })
}

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

    const EXE: &str = "sccache-dist";

    fn auth_generate_shared_tokens_bits(bit_val: &'static str) -> Vec<&'static str> {
        vec![EXE, "auth", "generate-shared-token", "--bits", bit_val]
    }

    fn auth_generate_jwt_hs256_server_token(subcommand_args: &[&'static str]) -> Vec<&'static str> {
        let mut args = vec![EXE, "auth", "generate-jwt-hs256-server-token"];
        args.extend_from_slice(subcommand_args);
        args
    }

    #[test]
    fn debug_assert() {
        get_clap_command().debug_assert()
    }

    #[test]
    fn missing_required_subcommands_fails() {
        let args_sets = &[vec![EXE], vec![EXE, "auth"]];

        for args in args_sets {
            assert!(try_parse_from(args).is_err());
        }
    }

    #[test]
    fn invalid_token_bits_fails() {
        let invalid_vals = vec!["not_a_num", "58", "8000", "70"];

        for invalid_val in invalid_vals {
            let args = auth_generate_shared_tokens_bits(invalid_val);
            assert!(try_parse_from(args).is_err());
        }
    }

    #[test]
    fn auth_generate_server_token_needs_key_source() {
        let server_args = &["--server", "127.0.0.1:4321"];

        let no_key = auth_generate_jwt_hs256_server_token(server_args);
        assert!(try_parse_from(no_key).is_err());

        let mut too_many_keys = auth_generate_jwt_hs256_server_token(server_args);
        too_many_keys.extend_from_slice(&["--secret-key", "secret", "--config", "some/path.toml"]);
        assert!(try_parse_from(too_many_keys).is_err());
    }

    // This is all just to work around `PartialEq` not being on some of the values used in variants
    // for `Command` yet
    fn assert_args_parse_to_auth(args: Vec<&'static str>, ideal_auth: AuthSubcommand) {
        match try_parse_from(&args) {
            Ok(Command::Auth(auth)) => assert_eq!(auth, ideal_auth),
            _ => panic!("Bad parsing for: {:#?}", args),
        }
    }

    #[test]
    fn auth_generate_jwt_hs256_key_good() {
        let args = vec![EXE, "auth", "generate-jwt-hs256-key"];

        assert_args_parse_to_auth(args, AuthSubcommand::Base64 { num_bytes: 256 / 8 });
    }

    #[test]
    fn auth_generate_jwt_hs256_server_token_good() {
        let base = auth_generate_jwt_hs256_server_token(&["--server", "127.0.0.1:4321"]);
        let server_socket: SocketAddr = "127.0.0.1:4321".parse().unwrap();
        let server_id = ServerId::new(server_socket);

        let mut secret_key = base.clone();
        secret_key.extend_from_slice(&["--secret-key", "very secret"]);
        assert_args_parse_to_auth(
            secret_key,
            AuthSubcommand::JwtHS256ServerToken {
                server_id,
                secret_key: "very secret".to_owned(),
            },
        );
    }

    #[test]
    fn auth_generate_shared_token_good() {
        let raw_to_expected_bit_vals = &[
            ("64", 64 / 8),
            ("128", 128 / 8),
            ("136", 136 / 8),
            ("4000", 4_000 / 8),
        ];

        for (raw, expected) in raw_to_expected_bit_vals {
            let args = auth_generate_shared_tokens_bits(raw);
            assert_args_parse_to_auth(
                args,
                AuthSubcommand::Base64 {
                    num_bytes: *expected,
                },
            );
        }
    }
}
<|endoftext|>


<commit_before>use std::ops::Deref;
use std::ops::DerefMut;
use std::cmp;

use game::*;
use game::data::*;
use ecs::*;
use behaviour::LeafResolution;
use direction::{self, Direction};

pub fn player_input<K: KnowledgeRenderer, I: 'static + InputSource + Clone>(input_source: I) -> BehaviourLeaf<K> {
    BehaviourLeaf::new(move |input| {
        loop {
            if let Some(meta_action) = get_meta_action(input, input_source.clone()) {
                return LeafResolution::Yield(meta_action);
            }
        }
    })
}

fn get_direction<I: InputSource>(map: &ControlMap, mut input_source: I) -> Option<Direction> {
    input_source.next_input().and_then(|event| {
        map.get(event).and_then(|control| {
            control_to_direction(control)
        })
    })
}

fn control_to_direction(control: Control) -> Option<Direction> {
    match control {
        Control::Direction(d) => Some(d),
        _ => None,
    }
}

fn display_message_log<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I, map: &ControlMap) {

    let mut renderer = input.renderer.borrow_mut();
    let message_log = input.entity.message_log_borrow().unwrap();

    let mut offset = 0;
    let num_lines = renderer.fullscreen_log_num_rows();
    let num_messages = message_log.len();
    let max_offset = if num_messages > num_lines {
        num_messages - num_lines
    } else {
        0
    };

    loop {
        renderer.publish_fullscreen_log(message_log.deref(), offset, input.language);

        if let Some(event) = input_source.next_input() {
            if let Some(control) = map.get(event) {
                match control {
                    Control::Pause |
                        Control::DisplayMessageLog => break,
                    Control::Direction(Direction::North) => {
                        offset = cmp::min(max_offset, offset + 1);
                    }
                    Control::Direction(Direction::South) => {
                        if offset > 0 {
                            offset -= 1;
                        }
                    }
                    _ => {}
                }
            }
        }
    }

    renderer.publish_all_windows(input.entity, input.language);
}

fn aim<R: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<R>, map: &ControlMap, mut input_source: I) -> Option<(EntityId, Direction)> {

    let mut renderer = input.renderer.borrow_mut();
    let mut message_log = input.entity.message_log_borrow_mut().expect("Expected component message_log");

    message_log.add_temporary(MessageType::ChooseDirection);
    renderer.update_log_buffer(message_log.deref(), input.language);
    renderer.draw_log();
    renderer.publish();

    let mut should_clear_log = true;

    let ret = input_source.next_input().and_then(|event| {
        map.get(event).and_then(|control| {
            match control {
                Control::Direction(direction) => {
                    let weapon_slots = input.entity.weapon_slots_borrow().expect("Expected component weapon_slots");
                    if let Some(weapon) = weapon_slots.get(direction) {
                        Some((*weapon, direction))
                    } else {
                        message_log.add_temporary(MessageType::EmptyWeaponSlotMessage);
                        should_clear_log = false;
                        None
                    }
                }
                _ => None,
            }
        })
    });

    if should_clear_log {
        message_log.add_temporary(MessageType::Empty);
    }
    renderer.update_log_buffer(message_log.deref(), input.language);
    renderer.draw_log();
    renderer.publish();

    ret
}

fn direction_to_relative_message(direction: Direction) -> MessageType {
    match direction {
        Direction::East => MessageType::Front,
        Direction::West => MessageType::Rear,
        Direction::North => MessageType::Left,
        Direction::South => MessageType::Right,
        _ => panic!(),
    }
}

fn display_status<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I) {
    let mut renderer = input.renderer.borrow_mut();
    let weapon_slots = input.entity.weapon_slots_borrow().expect("Expected component weapon_slots");

    let mut message = Message::new();

    for d in direction::cardinal_direction_iter() {
        let m = direction_to_relative_message(d);
        input.language.translate(m, &mut message);
        message.push(MessagePart::plain(": "));

        if let Some(weapon_id) = weapon_slots.get(d) {
            let name = input.ecs.name(*weapon_id).expect("Expected component name");
            input.language.translate(MessageType::Name(name), &mut message);
        } else {
            input.language.translate(MessageType::EmptyWeaponSlot, &mut message);
        }
        message.push(MessagePart::Newline);
    }

    display_message_scrolling(renderer.deref_mut(), &mut input_source, &message, true);
    renderer.publish_all_windows(input.entity, input.language);
}

fn get_meta_action<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I) -> Option<MetaAction> {

    // clear temporary messages from the log
    input.entity.message_log_borrow_mut().expect("Expected component message_log").add_temporary(MessageType::Empty);

    input_source.next_input().and_then(|event| {
        if event == InputEvent::Quit {
            return Some(MetaAction::External(External::Quit));
        }
        input.entity.control_map_borrow().and_then(|map_ref| {
            let map = map_ref.deref();
            map.get(event).and_then(|control| {
                match control {
                    Control::Direction(Direction::East) => Some(MetaAction::ActionArgs(ActionArgs::ChangeSpeed(input.entity.id(), ChangeSpeed::Accelerate))),
                    Control::Direction(Direction::West) => Some(MetaAction::ActionArgs(ActionArgs::ChangeSpeed(input.entity.id(), ChangeSpeed::Decelerate))),
                    Control::Direction(Direction::North) => Some(MetaAction::ActionArgs(ActionArgs::Steer(input.entity.id(), SteerDirection::Up))),
                    Control::Direction(Direction::South) => Some(MetaAction::ActionArgs(ActionArgs::Steer(input.entity.id(), SteerDirection::Down))),
                    Control::Fire => {
                        aim(input, map, input_source).map(|(gun_id, direction)| {
                            MetaAction::ActionArgs(ActionArgs::FireGun {
                                gun_id: gun_id,
                                shooter_id: input.entity.id(),
                                direction: direction,
                            })
                        })
                    }
                    Control::Wait => {
                        Some(MetaAction::ActionArgs(ActionArgs::Null))
                    }
                    Control::Pause => Some(MetaAction::External(External::Pause)),
                    Control::DisplayMessageLog => {
                        display_message_log(input, input_source, map);
                        None
                    }
                    Control::Status => {
                        display_status(input, input_source);
                        None
                    }
                    _ => None,
                }
            })
        })
    })
}
<commit_msg>Fixed rendering bug on macOS<commit_after>use std::ops::Deref;
use std::ops::DerefMut;
use std::cmp;

use game::*;
use game::data::*;
use ecs::*;
use behaviour::LeafResolution;
use direction::{self, Direction};

pub fn player_input<K: KnowledgeRenderer, I: 'static + InputSource + Clone>(input_source: I) -> BehaviourLeaf<K> {
    BehaviourLeaf::new(move |input| {
        loop {
            if let Some(meta_action) = get_meta_action(input, input_source.clone()) {
                return LeafResolution::Yield(meta_action);
            }
        }
    })
}

fn get_direction<I: InputSource>(map: &ControlMap, mut input_source: I) -> Option<Direction> {
    input_source.next_input().and_then(|event| {
        map.get(event).and_then(|control| {
            control_to_direction(control)
        })
    })
}

fn control_to_direction(control: Control) -> Option<Direction> {
    match control {
        Control::Direction(d) => Some(d),
        _ => None,
    }
}

fn display_message_log<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I, map: &ControlMap) {

    let mut renderer = input.renderer.borrow_mut();
    let message_log = input.entity.message_log_borrow().unwrap();

    let mut offset = 0;
    let num_lines = renderer.fullscreen_log_num_rows();
    let num_messages = message_log.len();
    let max_offset = if num_messages > num_lines {
        num_messages - num_lines
    } else {
        0
    };

    loop {
        renderer.publish_fullscreen_log(message_log.deref(), offset, input.language);

        if let Some(event) = input_source.next_input() {
            if let Some(control) = map.get(event) {
                match control {
                    Control::Pause |
                        Control::DisplayMessageLog => break,
                    Control::Direction(Direction::North) => {
                        offset = cmp::min(max_offset, offset + 1);
                    }
                    Control::Direction(Direction::South) => {
                        if offset > 0 {
                            offset -= 1;
                        }
                    }
                    _ => {}
                }
            }
        }
    }

    renderer.publish_all_windows(input.entity, input.language);
}

fn aim<R: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<R>, map: &ControlMap, mut input_source: I) -> Option<(EntityId, Direction)> {

    let mut renderer = input.renderer.borrow_mut();
    let mut message_log = input.entity.message_log_borrow_mut().expect("Expected component message_log");

    message_log.add_temporary(MessageType::ChooseDirection);
    renderer.update_log_buffer(message_log.deref(), input.language);
    renderer.draw_log();
    renderer.publish_all_windows(input.entity, input.language);

    let mut should_clear_log = true;

    let ret = input_source.next_input().and_then(|event| {
        map.get(event).and_then(|control| {
            match control {
                Control::Direction(direction) => {
                    let weapon_slots = input.entity.weapon_slots_borrow().expect("Expected component weapon_slots");
                    if let Some(weapon) = weapon_slots.get(direction) {
                        Some((*weapon, direction))
                    } else {
                        message_log.add_temporary(MessageType::EmptyWeaponSlotMessage);
                        should_clear_log = false;
                        None
                    }
                }
                _ => None,
            }
        })
    });

    if should_clear_log {
        message_log.add_temporary(MessageType::Empty);
    }
    renderer.update_log_buffer(message_log.deref(), input.language);
    renderer.draw_log();
    renderer.publish_all_windows(input.entity, input.language);

    ret
}

fn direction_to_relative_message(direction: Direction) -> MessageType {
    match direction {
        Direction::East => MessageType::Front,
        Direction::West => MessageType::Rear,
        Direction::North => MessageType::Left,
        Direction::South => MessageType::Right,
        _ => panic!(),
    }
}

fn display_status<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I) {
    let mut renderer = input.renderer.borrow_mut();
    let weapon_slots = input.entity.weapon_slots_borrow().expect("Expected component weapon_slots");

    let mut message = Message::new();

    for d in direction::cardinal_direction_iter() {
        let m = direction_to_relative_message(d);
        input.language.translate(m, &mut message);
        message.push(MessagePart::plain(": "));

        if let Some(weapon_id) = weapon_slots.get(d) {
            let name = input.ecs.name(*weapon_id).expect("Expected component name");
            input.language.translate(MessageType::Name(name), &mut message);
        } else {
            input.language.translate(MessageType::EmptyWeaponSlot, &mut message);
        }
        message.push(MessagePart::Newline);
    }

    display_message_scrolling(renderer.deref_mut(), &mut input_source, &message, true);
    renderer.publish_all_windows(input.entity, input.language);
}

fn get_meta_action<K: KnowledgeRenderer, I: InputSource>(input: BehaviourInput<K>, mut input_source: I) -> Option<MetaAction> {

    // clear temporary messages from the log
    input.entity.message_log_borrow_mut().expect("Expected component message_log").add_temporary(MessageType::Empty);

    input_source.next_input().and_then(|event| {
        if event == InputEvent::Quit {
            return Some(MetaAction::External(External::Quit));
        }
        input.entity.control_map_borrow().and_then(|map_ref| {
            let map = map_ref.deref();
            map.get(event).and_then(|control| {
                match control {
                    Control::Direction(Direction::East) => Some(MetaAction::ActionArgs(ActionArgs::ChangeSpeed(input.entity.id(), ChangeSpeed::Accelerate))),
                    Control::Direction(Direction::West) => Some(MetaAction::ActionArgs(ActionArgs::ChangeSpeed(input.entity.id(), ChangeSpeed::Decelerate))),
                    Control::Direction(Direction::North) => Some(MetaAction::ActionArgs(ActionArgs::Steer(input.entity.id(), SteerDirection::Up))),
                    Control::Direction(Direction::South) => Some(MetaAction::ActionArgs(ActionArgs::Steer(input.entity.id(), SteerDirection::Down))),
                    Control::Fire => {
                        aim(input, map, input_source).map(|(gun_id, direction)| {
                            MetaAction::ActionArgs(ActionArgs::FireGun {
                                gun_id: gun_id,
                                shooter_id: input.entity.id(),
                                direction: direction,
                            })
                        })
                    }
                    Control::Wait => {
                        Some(MetaAction::ActionArgs(ActionArgs::Null))
                    }
                    Control::Pause => Some(MetaAction::External(External::Pause)),
                    Control::DisplayMessageLog => {
                        display_message_log(input, input_source, map);
                        None
                    }
                    Control::Status => {
                        display_status(input, input_source);
                        None
                    }
                    _ => None,
                }
            })
        })
    })
}
<|endoftext|>


<commit_before>use std::char;
use nom::{self, InputLength};
use parser::errors::{ErrorKind, is_custom};
use parser::trivia::{ws, ws_newlines, newline};
use parser::Span;
use ::decor::InternalString;

// ;; String

// string = ml-basic-string / basic-string / ml-literal-string / literal-string
named!(pub string(Span) -> InternalString,
       alt_complete!(
           ml_basic_string
         | basic_string
         | ml_literal_string => { |s: Span| InternalString::from(s.fragment) }
         | literal_string    => { |s: Span| InternalString::from(s.fragment) }
       )
);

// basic-unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
#[inline]
fn is_basic_unescaped(c: char) -> bool {
    match c {
        '\u{20}'...'\u{21}' |
        '\u{23}'...'\u{5B}' |
        '\u{5D}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

// escape = %x5C                    ; \
const ESCAPE: &str = "\\";

// escaped = escape ( %x22 /          ; "    quotation mark  U+0022
//                    %x5C /          ; \    reverse solidus U+005C
//                    %x2F /          ; /    solidus         U+002F
//                    %x62 /          ; b    backspace       U+0008
//                    %x66 /          ; f    form feed       U+000C
//                    %x6E /          ; n    line feed       U+000A
//                    %x72 /          ; r    carriage return U+000D
//                    %x74 /          ; t    tab             U+0009
//                    %x75 4HEXDIG /  ; uXXXX                U+XXXX
//                    %x55 8HEXDIG )  ; UXXXXXXXX            U+XXXXXXXX
named!(pub escaped(Span) -> char,
       do_parse!(
           complete!(tag!(ESCAPE)) >>
       c:  alt_custom!(e!(ErrorKind::InvalidEscapeChar),
                 tag!("\"") => { |_| '"'     }
               | tag!("\\") => { |_| '\\'    }
               | tag!("b")  => { |_| '\u{8}' } // \b
               | tag!("f")  => { |_| '\u{c}' } // \f
               | tag!("n")  => { |_| '\n'    }
               | tag!("r")  => { |_| '\r'    }
               | tag!("t")  => { |_| '\t'    }
               | do_parse!(
                   complete!(tag!("u"))      >>
                c: call!(parse_hexescape, 4) >>
                   (c)
                 )
               | do_parse!(
                   complete!(tag!("U"))      >>
                c: call!(parse_hexescape, 8) >>
                   (c)
                 )
           ) >>
           (c)
       )
);

fn parse_hexescape(input: Span, n: u8) -> nom::IResult<Span, char> {
    do_parse!(
        input,
     s: complete!(take!(n)) >>
     h: expr_res!(u32::from_str_radix(s.fragment, 16)) >>
     c: expr_opt!(char::from_u32(h)) >>
        (c)
    )
}

named!(#[inline], pub basic_unescaped(Span) -> Span,
       take_while1!(is_basic_unescaped)
);

// argh, https://github.com/rust-lang/rfcs/issues/1230
enum SpanOrChar<'a> {
    Span(Span<'a>),
    Char(char),
}

// basic-char = basic-unescaped / escaped
named!(#[inline], basic_chars(Span) -> SpanOrChar,
       do_parse!(
           not!(complete!(char!('"'))) >>
        c: alt_custom!(e!(ErrorKind::InvalidCharInString),
                       escaped           => { |c| SpanOrChar::Char(c) }
                     | basic_unescaped   => { |s| SpanOrChar::Span(s) }
           ) >>
           (c)
       )
);

// quotation-mark = %x22            ; "
const QUOTATION_MARK: &str = "\"";

#[inline]
fn escape_folder(mut acc: InternalString, c: &SpanOrChar) -> InternalString {
    match *c {
        SpanOrChar::Char(c) => acc.push(c),
        SpanOrChar::Span(s) => acc.push_str(s.fragment),
    }
    acc
}

// basic-string = quotation-mark *basic-char quotation-mark
named!(pub basic_string(Span) -> InternalString,
       delimited!(
           complete!(tag!(QUOTATION_MARK)),
           fold_many0_custom!(
               basic_chars,
               InternalString::new(),
               escape_folder
           ),
           err!(ErrorKind::UnterminatedString,
                complete!(tag!(QUOTATION_MARK)))
       )
);

// ;; Multiline Basic String

// note: this rule is modified (quote isn't included)
// ml-basic-unescaped = %x20-5B / %x5D-10FFFF
#[inline]
fn is_ml_basic_unescaped(c: char) -> bool {
    match c {
        '\u{20}'...'\u{21}' |
        '\u{23}'...'\u{5B}' |
        '\u{5D}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(#[inline], ml_basic_unescaped(Span) -> Span,
       take_while1!(is_ml_basic_unescaped)
);

// ml-basic-char = ml-basic-unescaped / escaped
named!(#[inline], ml_basic_chars_or_newline(Span) -> SpanOrChar,
       alt_custom!(e!(ErrorKind::InvalidCharInString),
           // TOML parsers should feel free to normalize newline
           // to whatever makes sense for their platform.
           newline            => { |_| SpanOrChar::Char('\n') }
         | tag!("\"")         => { |_| SpanOrChar::Char('"') }
         | escaped            => { |c| SpanOrChar::Char(c) }
         | ml_basic_unescaped => { |s| SpanOrChar::Span(s) }
       )
);

// ml-basic-string-delim = 3quotation-mark
const ML_BASIC_STRING_DELIM: &str = "\"\"\"";

// When the last non-whitespace character on a line is a \,
// it will be trimmed along with all whitespace
// (including newlines) up to the next non-whitespace
// character or closing delimiter.
named!(#[inline], eat_escaped_newline(Span) -> (),
       fold_many0!(
           tuple!(
               tag!(ESCAPE),
               ws,
               ws_newlines
           ),
        // (\/)_(;;)_(\/)
           (), |_, _| ()
       )
);

// ml-basic-body = *( ( escape ws-newline ) / ml-basic-char / newline )
named!(ml_basic_body(Span) -> InternalString,
       do_parse!(
           //  A newline immediately following the opening delimiter will be trimmed.
           opt!(complete!(newline)) >>
           eat_escaped_newline >>
           body: fold_many0_custom!(
               do_parse!(
                   not!(tag!(ML_BASIC_STRING_DELIM)) >>
                c: ml_basic_chars_or_newline         >>
                   eat_escaped_newline               >>
                   (c)
               ),
               InternalString::new(),
               escape_folder
           ) >>
           (body)
       )
);

// ml-basic-string = ml-basic-string-delim ml-basic-body ml-basic-string-delim
named!(pub ml_basic_string(Span) -> InternalString,
       delimited!(
           complete!(tag!(ML_BASIC_STRING_DELIM)),
           ml_basic_body,
           err!(ErrorKind::UnterminatedString,
                complete!(tag!(ML_BASIC_STRING_DELIM)))
       )
);

// ;; Literal String

// apostrophe = %x27 ; ' apostrophe
const APOSTROPHE: &str = "'";

// literal-char = %x09 / %x20-26 / %x28-10FFFF
fn is_literal_char(c: char) -> bool {
    match c {
        '\u{09}' |
        '\u{20}'...'\u{26}' |
        '\u{28}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(parse_literal_body(Span) -> Span,
       take_while!(is_literal_char)
);

macro_rules! string_literal_body (
    ($input:expr, $delim:ident, $verify:ident) => (
        do_parse!(
            $input,
         s: err!(ErrorKind::UnterminatedString,
                 complete!(take_until!($delim))) >>
            err!(ErrorKind::InvalidCharInString,
                 expr_opt!({
                     match $verify(s) {
                         nom::IResult::Done(rem, _) if rem.input_len() == 0 => {
                             Some(())
                         },
                         _ => None,
                     }
                 })
            ) >>
            (s)
        )
    );
);

// literal-string = apostrophe *literal-char apostrophe
named!(pub literal_string(Span) -> Span,
       delimited!(
           complete!(tag!(APOSTROPHE)),
           string_literal_body!(APOSTROPHE, parse_literal_body),
           tag!(APOSTROPHE)
       )
);

// ;; Multiline Literal String

// ml-literal-string-delim = 3apostrophe
const ML_LITERAL_STRING_DELIM: &str = "'''";

// ml-literal-char = %x09 / %x20-10FFFF
fn is_ml_literal_char(c: char) -> bool {
    match c {
        '\u{09}' | '\u{20}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(parse_ml_literal_body(Span) -> (),
       fold_many0!(
           alt_complete!(
               take_while1!(is_ml_literal_char)
             | newline
           ), (), |_, _| ()
       )
);

// ml-literal-body = *( ml-literal-char / newline )
named!(pub ml_literal_body(Span) -> Span,
       do_parse!(
           //  A newline immediately following the opening delimiter will be trimmed.
           opt!(complete!(newline))                      >>
     body: string_literal_body!(ML_LITERAL_STRING_DELIM,
                                parse_ml_literal_body)   >>
           (body)
       )
);

// ml-literal-string = ml-literal-string-delim ml-literal-body ml-literal-string-delim
named!(pub ml_literal_string(Span) -> Span,
       delimited!(
           complete!(tag!(ML_LITERAL_STRING_DELIM)),
           ml_literal_body,
           tag!(ML_LITERAL_STRING_DELIM)
       )
);
<commit_msg>fix(parser): replace windows newlines in ml_literal_string with \n<commit_after>use std::char;
use nom::{self, InputLength};
use parser::errors::{ErrorKind, is_custom};
use parser::trivia::{ws, ws_newlines, newline};
use parser::Span;
use ::decor::InternalString;

// ;; String

// string = ml-basic-string / basic-string / ml-literal-string / literal-string
named!(pub string(Span) -> InternalString,
       alt_complete!(
           ml_basic_string
         | basic_string
         | ml_literal_string => { |s: Span| InternalString::from(s.fragment.replace("\r\n", "\n")) }
         | literal_string    => { |s: Span| InternalString::from(s.fragment) }
       )
);

// basic-unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
#[inline]
fn is_basic_unescaped(c: char) -> bool {
    match c {
        '\u{20}'...'\u{21}' |
        '\u{23}'...'\u{5B}' |
        '\u{5D}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

// escape = %x5C                    ; \
const ESCAPE: &str = "\\";

// escaped = escape ( %x22 /          ; "    quotation mark  U+0022
//                    %x5C /          ; \    reverse solidus U+005C
//                    %x2F /          ; /    solidus         U+002F
//                    %x62 /          ; b    backspace       U+0008
//                    %x66 /          ; f    form feed       U+000C
//                    %x6E /          ; n    line feed       U+000A
//                    %x72 /          ; r    carriage return U+000D
//                    %x74 /          ; t    tab             U+0009
//                    %x75 4HEXDIG /  ; uXXXX                U+XXXX
//                    %x55 8HEXDIG )  ; UXXXXXXXX            U+XXXXXXXX
named!(pub escaped(Span) -> char,
       do_parse!(
           complete!(tag!(ESCAPE)) >>
       c:  alt_custom!(e!(ErrorKind::InvalidEscapeChar),
                 tag!("\"") => { |_| '"'     }
               | tag!("\\") => { |_| '\\'    }
               | tag!("b")  => { |_| '\u{8}' } // \b
               | tag!("f")  => { |_| '\u{c}' } // \f
               | tag!("n")  => { |_| '\n'    }
               | tag!("r")  => { |_| '\r'    }
               | tag!("t")  => { |_| '\t'    }
               | do_parse!(
                   complete!(tag!("u"))      >>
                c: call!(parse_hexescape, 4) >>
                   (c)
                 )
               | do_parse!(
                   complete!(tag!("U"))      >>
                c: call!(parse_hexescape, 8) >>
                   (c)
                 )
           ) >>
           (c)
       )
);

fn parse_hexescape(input: Span, n: u8) -> nom::IResult<Span, char> {
    do_parse!(
        input,
     s: complete!(take!(n)) >>
     h: expr_res!(u32::from_str_radix(s.fragment, 16)) >>
     c: expr_opt!(char::from_u32(h)) >>
        (c)
    )
}

named!(#[inline], pub basic_unescaped(Span) -> Span,
       take_while1!(is_basic_unescaped)
);

// argh, https://github.com/rust-lang/rfcs/issues/1230
enum SpanOrChar<'a> {
    Span(Span<'a>),
    Char(char),
}

// basic-char = basic-unescaped / escaped
named!(#[inline], basic_chars(Span) -> SpanOrChar,
       do_parse!(
           not!(complete!(char!('"'))) >>
        c: alt_custom!(e!(ErrorKind::InvalidCharInString),
                       escaped           => { |c| SpanOrChar::Char(c) }
                     | basic_unescaped   => { |s| SpanOrChar::Span(s) }
           ) >>
           (c)
       )
);

// quotation-mark = %x22            ; "
const QUOTATION_MARK: &str = "\"";

#[inline]
fn escape_folder(mut acc: InternalString, c: &SpanOrChar) -> InternalString {
    match *c {
        SpanOrChar::Char(c) => acc.push(c),
        SpanOrChar::Span(s) => acc.push_str(s.fragment),
    }
    acc
}

// basic-string = quotation-mark *basic-char quotation-mark
named!(pub basic_string(Span) -> InternalString,
       delimited!(
           complete!(tag!(QUOTATION_MARK)),
           fold_many0_custom!(
               basic_chars,
               InternalString::new(),
               escape_folder
           ),
           err!(ErrorKind::UnterminatedString,
                complete!(tag!(QUOTATION_MARK)))
       )
);

// ;; Multiline Basic String

// note: this rule is modified (quote isn't included)
// ml-basic-unescaped = %x20-5B / %x5D-10FFFF
#[inline]
fn is_ml_basic_unescaped(c: char) -> bool {
    match c {
        '\u{20}'...'\u{21}' |
        '\u{23}'...'\u{5B}' |
        '\u{5D}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(#[inline], ml_basic_unescaped(Span) -> Span,
       take_while1!(is_ml_basic_unescaped)
);

// ml-basic-char = ml-basic-unescaped / escaped
named!(#[inline], ml_basic_chars_or_newline(Span) -> SpanOrChar,
       alt_custom!(e!(ErrorKind::InvalidCharInString),
           // TOML parsers should feel free to normalize newline
           // to whatever makes sense for their platform.
           newline            => { |_| SpanOrChar::Char('\n') }
         | tag!("\"")         => { |_| SpanOrChar::Char('"') }
         | escaped            => { |c| SpanOrChar::Char(c) }
         | ml_basic_unescaped => { |s| SpanOrChar::Span(s) }
       )
);

// ml-basic-string-delim = 3quotation-mark
const ML_BASIC_STRING_DELIM: &str = "\"\"\"";

// When the last non-whitespace character on a line is a \,
// it will be trimmed along with all whitespace
// (including newlines) up to the next non-whitespace
// character or closing delimiter.
named!(#[inline], eat_escaped_newline(Span) -> (),
       fold_many0!(
           tuple!(
               tag!(ESCAPE),
               ws,
               ws_newlines
           ),
        // (\/)_(;;)_(\/)
           (), |_, _| ()
       )
);

// ml-basic-body = *( ( escape ws-newline ) / ml-basic-char / newline )
named!(ml_basic_body(Span) -> InternalString,
       do_parse!(
           //  A newline immediately following the opening delimiter will be trimmed.
           opt!(complete!(newline)) >>
           eat_escaped_newline >>
           body: fold_many0_custom!(
               do_parse!(
                   not!(tag!(ML_BASIC_STRING_DELIM)) >>
                c: ml_basic_chars_or_newline         >>
                   eat_escaped_newline               >>
                   (c)
               ),
               InternalString::new(),
               escape_folder
           ) >>
           (body)
       )
);

// ml-basic-string = ml-basic-string-delim ml-basic-body ml-basic-string-delim
named!(pub ml_basic_string(Span) -> InternalString,
       delimited!(
           complete!(tag!(ML_BASIC_STRING_DELIM)),
           ml_basic_body,
           err!(ErrorKind::UnterminatedString,
                complete!(tag!(ML_BASIC_STRING_DELIM)))
       )
);

// ;; Literal String

// apostrophe = %x27 ; ' apostrophe
const APOSTROPHE: &str = "'";

// literal-char = %x09 / %x20-26 / %x28-10FFFF
fn is_literal_char(c: char) -> bool {
    match c {
        '\u{09}' |
        '\u{20}'...'\u{26}' |
        '\u{28}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(parse_literal_body(Span) -> Span,
       take_while!(is_literal_char)
);

macro_rules! string_literal_body (
    ($input:expr, $delim:ident, $verify:ident) => (
        do_parse!(
            $input,
         s: err!(ErrorKind::UnterminatedString,
                 complete!(take_until!($delim))) >>
            err!(ErrorKind::InvalidCharInString,
                 expr_opt!({
                     match $verify(s) {
                         nom::IResult::Done(rem, _) if rem.input_len() == 0 => {
                             Some(())
                         },
                         _ => None,
                     }
                 })
            ) >>
            (s)
        )
    );
);

// literal-string = apostrophe *literal-char apostrophe
named!(pub literal_string(Span) -> Span,
       delimited!(
           complete!(tag!(APOSTROPHE)),
           string_literal_body!(APOSTROPHE, parse_literal_body),
           tag!(APOSTROPHE)
       )
);

// ;; Multiline Literal String

// ml-literal-string-delim = 3apostrophe
const ML_LITERAL_STRING_DELIM: &str = "'''";

// ml-literal-char = %x09 / %x20-10FFFF
fn is_ml_literal_char(c: char) -> bool {
    match c {
        '\u{09}' | '\u{20}'...'\u{10FFFF}' => true,
        _ => false,
    }
}

named!(parse_ml_literal_body(Span) -> (),
       fold_many0!(
           alt_complete!(
               take_while1!(is_ml_literal_char)
             | newline
           ), (), |_, _| ()
       )
);

// ml-literal-body = *( ml-literal-char / newline )
named!(pub ml_literal_body(Span) -> Span,
       do_parse!(
           //  A newline immediately following the opening delimiter will be trimmed.
           opt!(complete!(newline))                      >>
     body: string_literal_body!(ML_LITERAL_STRING_DELIM,
                                parse_ml_literal_body)   >>
           (body)
       )
);

// ml-literal-string = ml-literal-string-delim ml-literal-body ml-literal-string-delim
named!(pub ml_literal_string(Span) -> Span,
       delimited!(
           complete!(tag!(ML_LITERAL_STRING_DELIM)),
           ml_literal_body,
           tag!(ML_LITERAL_STRING_DELIM)
       )
);
<|endoftext|>