<commit_before>//! Module: czmq-zcert

use {czmq_sys, Error, ErrorKind, RawInterface, Result, Sockish, zmq, ZList};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::Path;
use std::{error, fmt, ptr, result, slice, str};

const KEY_SIZE: usize = 32;

#[derive(Debug, Eq)]
pub struct ZCert {
    zcert: *mut czmq_sys::zcert_t,
    owned: bool,
}

unsafe impl Send for ZCert {}
unsafe impl Sync for ZCert {}

impl Drop for ZCert {
    fn drop(&mut self) {
        if self.owned {
            unsafe { czmq_sys::zcert_destroy(&mut self.zcert) };
        }
    }
}

impl PartialEq for ZCert {
    fn eq(&self, other: &ZCert) -> bool {
        ZCert::eq(self, other)
    }
}

impl ZCert {
    pub fn new() -> Result<ZCert> {
        let zcert = unsafe { czmq_sys::zcert_new() };

        if zcert == ptr::null_mut() {
            return Err(Error::new(ErrorKind::NullPtr, ZCertError::Instantiate));
        }

        Ok(ZCert {
            zcert: zcert,
            owned: true,
        })
    }

    pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> ZCert {
        ZCert {
            zcert: unsafe { czmq_sys::zcert_new_from(public_key.as_ptr(), secret_key.as_ptr()) },
            owned: true,
        }
    }

    pub fn from_txt(public_txt: &str, secret_txt: &str) -> ZCert {
        ZCert::from_keys(&zmq::z85_decode(public_txt), &zmq::z85_decode(secret_txt))
    }

    pub fn load(filename: &str) -> Result<ZCert> {
        let filename_c = try!(CString::new(filename));
        let zcert = unsafe { czmq_sys::zcert_load(filename_c.as_ptr()) };

        if zcert == ptr::null_mut() {
            return Err(Error::new(ErrorKind::NullPtr, ZCertError::InvalidCert(filename_c.into_string().unwrap())));
        }

        Ok(ZCert {
            zcert: zcert,
            owned: true,
        })
    }

    pub fn public_key<'a>(&'a self) -> &'a [u8] {
        unsafe {
            let ptr = czmq_sys::zcert_public_key(self.zcert);
            slice::from_raw_parts(ptr, KEY_SIZE)
        }
    }

    pub fn secret_key<'a>(&'a self) -> &'a [u8] {
        unsafe {
            let ptr = czmq_sys::zcert_secret_key(self.zcert);
            slice::from_raw_parts(ptr, KEY_SIZE)
        }
    }

    pub fn public_txt<'a>(&'a self) -> &'a str {
        unsafe {
            let ptr = czmq_sys::zcert_public_txt(self.zcert);
            CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("")
        }
    }

    pub fn secret_txt<'a>(&'a self) -> &'a str {
        unsafe {
            let ptr = czmq_sys::zcert_secret_txt(self.zcert);
            CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("")
        }
    }

    pub fn set_meta(&self, key: &str, value: &str) {
        let key_c = CString::new(key).unwrap_or(CString::new("").unwrap());
        let value_c = CString::new(value).unwrap_or(CString::new("").unwrap());

        unsafe { czmq_sys::zcert_set_meta(self.zcert, key_c.as_ptr(), "%s\0".as_ptr() as *const i8, value_c.as_ptr()) };
    }

    pub fn meta(&self, key: &str) -> Option<result::Result<String, Vec<u8>>> {
        let key_c = CString::new(key).unwrap_or(CString::new("").unwrap());

        let ptr = unsafe { czmq_sys::zcert_meta(self.zcert, key_c.as_ptr()) };

        if ptr == ptr::null_mut() {
            None
        } else {
            let c_string = unsafe { CStr::from_ptr(ptr).to_owned() };
            let bytes = c_string.as_bytes().to_vec();
            match c_string.into_string() {
                Ok(s) => Some(Ok(s)),
                Err(_) => Some(Err(bytes)),
            }
        }
    }

    pub fn meta_keys(&self) -> ZList<&'static str> {
        let ptr = unsafe { czmq_sys::zcert_meta_keys(self.zcert) };
        ZList::<&'static str>::from_raw(ptr)
    }

    /// Encode certificate metadata into ZMQ wire format.
    ///
    /// ```no_run
    /// # use czmq::{ZCert, ZFrame, ZSock};
    /// # let sock = ZSock::new_rep("...").unwrap();
    /// let cert = ZCert::new().unwrap();
    /// cert.set_meta("key", "value");
    ///
    /// let encoded = cert.encode_meta();
    /// let frame = ZFrame::new(&encoded).unwrap();
    /// frame.send(&sock, None).unwrap();
    /// ```
    pub fn encode_meta(&self) -> Vec<u8> {
        let mut encoded: Vec<u8> = Vec::new();

        for metakey in self.meta_keys() {
            if let Some(Ok(metaval)) = self.meta(metakey) {
                encoded.push(metakey.len() as u8);
                encoded.extend_from_slice(metakey.as_bytes());
                encoded.push(((metaval.len() >> 24) & 0xff) as u8);
                encoded.push(((metaval.len() >> 16) & 0xff) as u8);
                encoded.push(((metaval.len() >> 8) & 0xff) as u8);
                encoded.push((metaval.len() & 0xff) as u8);
                encoded.extend_from_slice(metaval.as_bytes());
            }
        }

        encoded
    }

    /// Decode and set certificate metadata from ZMQ wire format.
    ///
    /// ```no_run
    /// # use czmq::{ZCert, ZFrame, ZSock};
    /// # let sock = ZSock::new_rep("...").unwrap();
    /// let frame = ZFrame::recv(&sock).unwrap();
    /// let encoded = match frame.data().unwrap() {
    ///     Ok(str) => str.into_bytes(),
    ///     Err(bytes) => bytes,
    /// };
    ///
    /// let cert = ZCert::new().unwrap();
    /// cert.decode_meta(&encoded).unwrap();
    /// ```
    pub fn decode_meta(&self, encoded: &[u8]) -> Result<()> {
        let mut bytes_left = encoded.len();
        let mut index = 0;

        while bytes_left > 1 {
            let name_length: usize = *try!(encoded.get(index).ok_or(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded))) as usize;
            index += 1;
            bytes_left -= 1;

            if bytes_left < name_length {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let name = try!(str::from_utf8(&encoded[index..index + name_length]));
            index += name_length;
            bytes_left -= name_length;

            if bytes_left < 4 {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let value_length = (((encoded[index] as u32) << 24) |
                                ((encoded[index + 1] as u32) << 16) |
                                ((encoded[index + 2] as u32) << 8) |
                                  encoded[index + 3] as u32) as usize;
            index += 4;
            bytes_left -= 4;

            if bytes_left < value_length {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let value = try!(str::from_utf8(&encoded[index..index + value_length]));
            index += value_length;
            bytes_left -= value_length;

            self.set_meta(name, value);
        }

        Ok(())
    }

    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn save_public<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save_public(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn save_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save_secret(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn apply<S: Sockish>(&self, sock: &S) {
        unsafe { czmq_sys::zcert_apply(self.zcert, sock.borrow_raw()) };
    }

    pub fn dup(&self) -> ZCert {
        let ptr = unsafe { czmq_sys::zcert_dup(self.zcert) };

        ZCert {
            zcert: ptr,
            owned: true,
        }
    }

    pub fn eq(&self, cert: &ZCert) -> bool {
        let result = unsafe { czmq_sys::zcert_eq(self.zcert, cert.zcert) };
        result == 1
    }

    pub fn print(&self) {
        unsafe { czmq_sys::zcert_print(self.zcert) };
    }
}

impl RawInterface<czmq_sys::zcert_t> for ZCert {
    fn from_raw(ptr: *mut czmq_sys::zcert_t, owned: bool) -> ZCert {
        ZCert {
            zcert: ptr,
            owned: owned,
        }
    }

    fn into_raw(mut self) -> *mut czmq_sys::zcert_t {
        self.owned = false;
        self.zcert
    }

    fn borrow_raw(&self) -> *mut czmq_sys::zcert_t {
        self.zcert
    }
}

#[derive(Debug)]
pub enum ZCertError {
    Instantiate,
    InvalidCert(String),
    InvalidMetaEncoded,
    SavePath(String),
}

impl fmt::Display for ZCertError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ZCertError::Instantiate => write!(f, "Could not instantiate new ZCert struct"),
            ZCertError::InvalidCert(ref e) => write!(f, "Could not open certificate at path: {}", e),
            ZCertError::InvalidMetaEncoded => write!(f, "Encoded metadata is invalid"),
            ZCertError::SavePath(ref e) => write!(f, "Could not save certificate file to path: {}", e),
        }
    }
}

impl error::Error for ZCertError {
    fn description(&self) -> &str {
        match *self {
            ZCertError::Instantiate => "Could not instantiate new ZCert struct",
            ZCertError::InvalidCert(_) => "Certificate was invalid or non-existent",
            ZCertError::InvalidMetaEncoded => "Encoded metadata is invalid",
            ZCertError::SavePath(_) => "Could not save certificate file to given path",
        }
    }
}

#[cfg(test)]
mod tests {
    use {ZSock, ZSys};
    use super::*;
    use zmq;

    const PUBLIC_TXT: &'static str = "Ko9/&3Uw)$U]Zyp>+4$-i/yaDea2QqlDPGl-&V1s";
    const SECRET_TXT: &'static str = "[MfOo!1^1N}zZY/x{[A6^9>VRC.+O6vX&]zYvDC-";

    #[test]
    fn test_public_key() {
        let cert = create_cert();
        let key = cert.public_key();
        let test_key = zmq::z85_decode(PUBLIC_TXT);

        let mut iter = 0;
        for _ in key.iter() {
            assert_eq!(key[iter], test_key[iter]);
            iter += 1;
        }
    }

    #[test]
    fn test_secret_key() {
        let cert = create_cert();
        let key = cert.secret_key();
        let test_key = zmq::z85_decode(SECRET_TXT);

        let mut iter = 0;
        for _ in key.iter() {
            assert_eq!(key[iter], test_key[iter]);
            iter += 1;
        }
    }

    #[test]
    fn test_public_txt() {
        let cert = create_cert();
        assert_eq!(cert.public_txt(), PUBLIC_TXT);
    }

    #[test]
    fn test_secret_txt() {
        let cert = create_cert();
        assert_eq!(cert.secret_txt(), SECRET_TXT);
    }

    #[test]
    fn test_getset_meta() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");
        assert_eq!(cert.meta("moo").unwrap().unwrap(), "cow");
    }

    #[test]
    fn test_meta_keys() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");

        let mut keys = cert.meta_keys();
        assert_eq!(keys.next().unwrap(), "moo");
    }

    #[test]
    fn test_encode_decode_meta() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");
        cert.set_meta("foobar", "baz");
        cert.set_meta("ka", "BOOM!!");
        let encoded = cert.encode_meta();

        let cert = create_cert();
        cert.decode_meta(&encoded).unwrap();

        assert_eq!(cert.meta("moo").unwrap().unwrap(), "cow");
        assert_eq!(cert.meta("foobar").unwrap().unwrap(), "baz");
        assert_eq!(cert.meta("ka").unwrap().unwrap(), "BOOM!!");
    }

    #[test]
    fn test_apply_zmq() {
        let cert = create_cert();
        let mut ctx = zmq::Context::new();
        let sock = ctx.socket(zmq::REQ).unwrap();
        cert.apply(&sock);
        assert_eq!(sock.get_curve_publickey().unwrap().unwrap(), PUBLIC_TXT);
        assert_eq!(sock.get_curve_secretkey().unwrap().unwrap(), SECRET_TXT);
    }

    #[test]
    fn test_apply_zsock() {
        ZSys::init();

        let cert = create_cert();
        let sock = ZSock::new_rep("inproc://zcert_test_apply_zsock").unwrap();
        cert.apply(&sock);
        assert_eq!(sock.curve_publickey().unwrap().unwrap(), PUBLIC_TXT);
        assert_eq!(sock.curve_secretkey().unwrap().unwrap(), SECRET_TXT);
    }

    #[test]
    fn test_dup() {
        let cert = create_cert();
        let dup = cert.dup();
        assert_eq!(cert.secret_txt(), dup.secret_txt());
    }

    #[test]
    fn test_eq() {
        let c1 = create_cert();
        let c2 = create_cert();
        assert_eq!(c1, c2);
    }

    fn create_cert() -> ZCert {
        ZCert::from_txt(PUBLIC_TXT, SECRET_TXT)
    }
}
<commit_msg>More ZCert paths<commit_after>//! Module: czmq-zcert

use {czmq_sys, Error, ErrorKind, RawInterface, Result, Sockish, zmq, ZList};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::path::Path;
use std::{error, fmt, ptr, result, slice, str};

const KEY_SIZE: usize = 32;

#[derive(Debug, Eq)]
pub struct ZCert {
    zcert: *mut czmq_sys::zcert_t,
    owned: bool,
}

unsafe impl Send for ZCert {}
unsafe impl Sync for ZCert {}

impl Drop for ZCert {
    fn drop(&mut self) {
        if self.owned {
            unsafe { czmq_sys::zcert_destroy(&mut self.zcert) };
        }
    }
}

impl PartialEq for ZCert {
    fn eq(&self, other: &ZCert) -> bool {
        ZCert::eq(self, other)
    }
}

impl ZCert {
    pub fn new() -> Result<ZCert> {
        let zcert = unsafe { czmq_sys::zcert_new() };

        if zcert == ptr::null_mut() {
            return Err(Error::new(ErrorKind::NullPtr, ZCertError::Instantiate));
        }

        Ok(ZCert {
            zcert: zcert,
            owned: true,
        })
    }

    pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> ZCert {
        ZCert {
            zcert: unsafe { czmq_sys::zcert_new_from(public_key.as_ptr(), secret_key.as_ptr()) },
            owned: true,
        }
    }

    pub fn from_txt(public_txt: &str, secret_txt: &str) -> ZCert {
        ZCert::from_keys(&zmq::z85_decode(public_txt), &zmq::z85_decode(secret_txt))
    }

    pub fn load<P: AsRef<Path>>(path: P) -> Result<ZCert> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));
        let zcert = unsafe { czmq_sys::zcert_load(path_c.as_ptr()) };

        if zcert == ptr::null_mut() {
            return Err(Error::new(ErrorKind::NullPtr, ZCertError::InvalidCert(path_c.into_string().unwrap())));
        }

        Ok(ZCert {
            zcert: zcert,
            owned: true,
        })
    }

    pub fn public_key<'a>(&'a self) -> &'a [u8] {
        unsafe {
            let ptr = czmq_sys::zcert_public_key(self.zcert);
            slice::from_raw_parts(ptr, KEY_SIZE)
        }
    }

    pub fn secret_key<'a>(&'a self) -> &'a [u8] {
        unsafe {
            let ptr = czmq_sys::zcert_secret_key(self.zcert);
            slice::from_raw_parts(ptr, KEY_SIZE)
        }
    }

    pub fn public_txt<'a>(&'a self) -> &'a str {
        unsafe {
            let ptr = czmq_sys::zcert_public_txt(self.zcert);
            CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("")
        }
    }

    pub fn secret_txt<'a>(&'a self) -> &'a str {
        unsafe {
            let ptr = czmq_sys::zcert_secret_txt(self.zcert);
            CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("")
        }
    }

    pub fn set_meta(&self, key: &str, value: &str) {
        let key_c = CString::new(key).unwrap_or(CString::new("").unwrap());
        let value_c = CString::new(value).unwrap_or(CString::new("").unwrap());

        unsafe { czmq_sys::zcert_set_meta(self.zcert, key_c.as_ptr(), "%s\0".as_ptr() as *const i8, value_c.as_ptr()) };
    }

    pub fn meta(&self, key: &str) -> Option<result::Result<String, Vec<u8>>> {
        let key_c = CString::new(key).unwrap_or(CString::new("").unwrap());

        let ptr = unsafe { czmq_sys::zcert_meta(self.zcert, key_c.as_ptr()) };

        if ptr == ptr::null_mut() {
            None
        } else {
            let c_string = unsafe { CStr::from_ptr(ptr).to_owned() };
            let bytes = c_string.as_bytes().to_vec();
            match c_string.into_string() {
                Ok(s) => Some(Ok(s)),
                Err(_) => Some(Err(bytes)),
            }
        }
    }

    pub fn meta_keys(&self) -> ZList<&'static str> {
        let ptr = unsafe { czmq_sys::zcert_meta_keys(self.zcert) };
        ZList::<&'static str>::from_raw(ptr)
    }

    /// Encode certificate metadata into ZMQ wire format.
    ///
    /// ```no_run
    /// # use czmq::{ZCert, ZFrame, ZSock};
    /// # let sock = ZSock::new_rep("...").unwrap();
    /// let cert = ZCert::new().unwrap();
    /// cert.set_meta("key", "value");
    ///
    /// let encoded = cert.encode_meta();
    /// let frame = ZFrame::new(&encoded).unwrap();
    /// frame.send(&sock, None).unwrap();
    /// ```
    pub fn encode_meta(&self) -> Vec<u8> {
        let mut encoded: Vec<u8> = Vec::new();

        for metakey in self.meta_keys() {
            if let Some(Ok(metaval)) = self.meta(metakey) {
                encoded.push(metakey.len() as u8);
                encoded.extend_from_slice(metakey.as_bytes());
                encoded.push(((metaval.len() >> 24) & 0xff) as u8);
                encoded.push(((metaval.len() >> 16) & 0xff) as u8);
                encoded.push(((metaval.len() >> 8) & 0xff) as u8);
                encoded.push((metaval.len() & 0xff) as u8);
                encoded.extend_from_slice(metaval.as_bytes());
            }
        }

        encoded
    }

    /// Decode and set certificate metadata from ZMQ wire format.
    ///
    /// ```no_run
    /// # use czmq::{ZCert, ZFrame, ZSock};
    /// # let sock = ZSock::new_rep("...").unwrap();
    /// let frame = ZFrame::recv(&sock).unwrap();
    /// let encoded = match frame.data().unwrap() {
    ///     Ok(str) => str.into_bytes(),
    ///     Err(bytes) => bytes,
    /// };
    ///
    /// let cert = ZCert::new().unwrap();
    /// cert.decode_meta(&encoded).unwrap();
    /// ```
    pub fn decode_meta(&self, encoded: &[u8]) -> Result<()> {
        let mut bytes_left = encoded.len();
        let mut index = 0;

        while bytes_left > 1 {
            let name_length: usize = *try!(encoded.get(index).ok_or(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded))) as usize;
            index += 1;
            bytes_left -= 1;

            if bytes_left < name_length {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let name = try!(str::from_utf8(&encoded[index..index + name_length]));
            index += name_length;
            bytes_left -= name_length;

            if bytes_left < 4 {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let value_length = (((encoded[index] as u32) << 24) |
                                ((encoded[index + 1] as u32) << 16) |
                                ((encoded[index + 2] as u32) << 8) |
                                  encoded[index + 3] as u32) as usize;
            index += 4;
            bytes_left -= 4;

            if bytes_left < value_length {
                return Err(Error::new(ErrorKind::InvalidArg, ZCertError::InvalidMetaEncoded));
            }

            let value = try!(str::from_utf8(&encoded[index..index + value_length]));
            index += value_length;
            bytes_left -= value_length;

            self.set_meta(name, value);
        }

        Ok(())
    }

    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn save_public<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save_public(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn save_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path_c = try!(CString::new(path.as_ref().to_str().unwrap()));

        unsafe {
            let rc = czmq_sys::zcert_save_secret(self.zcert, path_c.as_ptr());
            if rc == -1 {
                Err(Error::new(ErrorKind::NonZero, ZCertError::SavePath(path_c.into_string().unwrap())))
            } else {
                Ok(())
            }
        }
    }

    pub fn apply<S: Sockish>(&self, sock: &S) {
        unsafe { czmq_sys::zcert_apply(self.zcert, sock.borrow_raw()) };
    }

    pub fn dup(&self) -> ZCert {
        let ptr = unsafe { czmq_sys::zcert_dup(self.zcert) };

        ZCert {
            zcert: ptr,
            owned: true,
        }
    }

    pub fn eq(&self, cert: &ZCert) -> bool {
        let result = unsafe { czmq_sys::zcert_eq(self.zcert, cert.zcert) };
        result == 1
    }

    pub fn print(&self) {
        unsafe { czmq_sys::zcert_print(self.zcert) };
    }
}

impl RawInterface<czmq_sys::zcert_t> for ZCert {
    fn from_raw(ptr: *mut czmq_sys::zcert_t, owned: bool) -> ZCert {
        ZCert {
            zcert: ptr,
            owned: owned,
        }
    }

    fn into_raw(mut self) -> *mut czmq_sys::zcert_t {
        self.owned = false;
        self.zcert
    }

    fn borrow_raw(&self) -> *mut czmq_sys::zcert_t {
        self.zcert
    }
}

#[derive(Debug)]
pub enum ZCertError {
    Instantiate,
    InvalidCert(String),
    InvalidMetaEncoded,
    SavePath(String),
}

impl fmt::Display for ZCertError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ZCertError::Instantiate => write!(f, "Could not instantiate new ZCert struct"),
            ZCertError::InvalidCert(ref e) => write!(f, "Could not open certificate at path: {}", e),
            ZCertError::InvalidMetaEncoded => write!(f, "Encoded metadata is invalid"),
            ZCertError::SavePath(ref e) => write!(f, "Could not save certificate file to path: {}", e),
        }
    }
}

impl error::Error for ZCertError {
    fn description(&self) -> &str {
        match *self {
            ZCertError::Instantiate => "Could not instantiate new ZCert struct",
            ZCertError::InvalidCert(_) => "Certificate was invalid or non-existent",
            ZCertError::InvalidMetaEncoded => "Encoded metadata is invalid",
            ZCertError::SavePath(_) => "Could not save certificate file to given path",
        }
    }
}

#[cfg(test)]
mod tests {
    use {ZSock, ZSys};
    use super::*;
    use zmq;

    const PUBLIC_TXT: &'static str = "Ko9/&3Uw)$U]Zyp>+4$-i/yaDea2QqlDPGl-&V1s";
    const SECRET_TXT: &'static str = "[MfOo!1^1N}zZY/x{[A6^9>VRC.+O6vX&]zYvDC-";

    #[test]
    fn test_public_key() {
        let cert = create_cert();
        let key = cert.public_key();
        let test_key = zmq::z85_decode(PUBLIC_TXT);

        let mut iter = 0;
        for _ in key.iter() {
            assert_eq!(key[iter], test_key[iter]);
            iter += 1;
        }
    }

    #[test]
    fn test_secret_key() {
        let cert = create_cert();
        let key = cert.secret_key();
        let test_key = zmq::z85_decode(SECRET_TXT);

        let mut iter = 0;
        for _ in key.iter() {
            assert_eq!(key[iter], test_key[iter]);
            iter += 1;
        }
    }

    #[test]
    fn test_public_txt() {
        let cert = create_cert();
        assert_eq!(cert.public_txt(), PUBLIC_TXT);
    }

    #[test]
    fn test_secret_txt() {
        let cert = create_cert();
        assert_eq!(cert.secret_txt(), SECRET_TXT);
    }

    #[test]
    fn test_getset_meta() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");
        assert_eq!(cert.meta("moo").unwrap().unwrap(), "cow");
    }

    #[test]
    fn test_meta_keys() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");

        let mut keys = cert.meta_keys();
        assert_eq!(keys.next().unwrap(), "moo");
    }

    #[test]
    fn test_encode_decode_meta() {
        let cert = create_cert();
        cert.set_meta("moo", "cow");
        cert.set_meta("foobar", "baz");
        cert.set_meta("ka", "BOOM!!");
        let encoded = cert.encode_meta();

        let cert = create_cert();
        cert.decode_meta(&encoded).unwrap();

        assert_eq!(cert.meta("moo").unwrap().unwrap(), "cow");
        assert_eq!(cert.meta("foobar").unwrap().unwrap(), "baz");
        assert_eq!(cert.meta("ka").unwrap().unwrap(), "BOOM!!");
    }

    #[test]
    fn test_apply_zmq() {
        let cert = create_cert();
        let mut ctx = zmq::Context::new();
        let sock = ctx.socket(zmq::REQ).unwrap();
        cert.apply(&sock);
        assert_eq!(sock.get_curve_publickey().unwrap().unwrap(), PUBLIC_TXT);
        assert_eq!(sock.get_curve_secretkey().unwrap().unwrap(), SECRET_TXT);
    }

    #[test]
    fn test_apply_zsock() {
        ZSys::init();

        let cert = create_cert();
        let sock = ZSock::new_rep("inproc://zcert_test_apply_zsock").unwrap();
        cert.apply(&sock);
        assert_eq!(sock.curve_publickey().unwrap().unwrap(), PUBLIC_TXT);
        assert_eq!(sock.curve_secretkey().unwrap().unwrap(), SECRET_TXT);
    }

    #[test]
    fn test_dup() {
        let cert = create_cert();
        let dup = cert.dup();
        assert_eq!(cert.secret_txt(), dup.secret_txt());
    }

    #[test]
    fn test_eq() {
        let c1 = create_cert();
        let c2 = create_cert();
        assert_eq!(c1, c2);
    }

    fn create_cert() -> ZCert {
        ZCert::from_txt(PUBLIC_TXT, SECRET_TXT)
    }
}
<|endoftext|>


<commit_before>//! Web interface of cratesfyi

pub mod page;

/// ctry! (cratesfyitry) is extremely similar to try! and itry!
/// except it returns an error page response instead of plain Err.
macro_rules! ctry {
    ($result:expr) => (match $result {
        Ok(v) => v,
        Err(e) => {
            return super::page::Page::new(format!("{:?}", e)).title("An error has occured")
                .set_status(::iron::status::BadRequest).to_resp("resp");
        }
    })
}

/// cexpect will check an option and if it's not Some
/// it will return an error page response
macro_rules! cexpect {
    ($option:expr) => (match $option {
        Some(v) => v,
        None => {
            return super::page::Page::new("Resource not found".to_owned())
                .title("An error has occured")
                .set_status(::iron::status::BadRequest).to_resp("resp");
        }
    })
}

/// Gets an extension from Request
macro_rules! extension {
    ($req:expr, $ext:ty) => (
        cexpect!($req.extensions.get::<$ext>())
        )
}

mod rustdoc;
mod releases;
mod crate_details;
mod source;
mod pool;
mod file;
mod builds;
mod error;
mod sitemap;

use std::env;
use std::error::Error;
use std::time::Duration;
use std::path::PathBuf;
use iron::prelude::*;
use iron::{Handler, status};
use router::{Router, NoRoute};
use staticfile::Static;
use handlebars_iron::{HandlebarsEngine, DirectorySource};
use time;
use postgres::Connection;
use semver::{Version, VersionReq};
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;

/// Duration of static files for staticfile and DatabaseFileHandler (in seconds)
const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12;   // 12 months
const STYLE_CSS: &'static str = include_str!(concat!(env!("OUT_DIR"), "/style.css"));

struct CratesfyiHandler {
    router_handler: Box<Handler>,
    database_file_handler: Box<Handler>,
    static_handler: Box<Handler>,
}

impl CratesfyiHandler {
    fn chain<H: Handler>(base: H) -> Chain {
        // TODO: Use DocBuilderOptions for paths
        let mut hbse = HandlebarsEngine::new();
        hbse.add(Box::new(DirectorySource::new("./templates", ".hbs")));

        // load templates
        if let Err(e) = hbse.reload() {
            panic!("Failed to load handlebar templates: {}", e.description());
        }

        let mut chain = Chain::new(base);
        chain.link_before(pool::Pool::new());
        chain.link_after(hbse);
        chain
    }

    pub fn new() -> CratesfyiHandler {
        let mut router = Router::new();
        router.get("/", releases::home_page);
        router.get("/style.css", style_css_handler);
        router.get("/about", |_: &mut Request| {
            page::Page::new(false).title("About Docs.rs").to_resp("about")
        });
        router.get("/robots.txt", sitemap::robots_txt_handler);
        router.get("/sitemap.xml", sitemap::sitemap_handler);
        router.get("/releases", releases::releases_handler);
        router.get("/releases/feed", releases::releases_feed_handler);
        router.get("/releases/recent/:page", releases::releases_handler);
        router.get("/releases/stars", releases::stars_handler);
        router.get("/releases/stars/:page", releases::stars_handler);
        router.get("/releases/:author", releases::author_handler);
        router.get("/releases/:author/:page", releases::author_handler);
        router.get("/releases/activity", releases::activity_handler);
        router.get("/releases/search", releases::search_handler);
        router.get("/crate/:name", crate_details::crate_details_handler);
        router.get("/crate/:name/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/builds", builds::build_list_handler);
        router.get("/crate/:name/:version/builds/:id", builds::build_list_handler);
        router.get("/crate/:name/:version/source/", source::source_browser_handler);
        router.get("/crate/:name/:version/source/*", source::source_browser_handler);
        router.get("/:crate", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/badge.svg", rustdoc::badge_handler);
        router.get("/:crate/:version", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/search-index.js", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/:target/", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target/*.html", rustdoc::rustdoc_html_server_handler);

        let router_chain = Self::chain(router);
        let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX").unwrap()).join("public_html");
        let static_handler = Static::new(prefix)
            .cache(Duration::from_secs(STATIC_FILE_CACHE_DURATION));

        CratesfyiHandler {
            router_handler: Box::new(router_chain),
            database_file_handler: Box::new(file::DatabaseFileHandler),
            static_handler: Box::new(static_handler),
        }
    }
}

impl Handler for CratesfyiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // try router first then db/static file handler
        // return 404 if none of them return Ok
        self.router_handler
            .handle(req)
            .or_else(|e| {
                // if router fails try to serve files from database first
                self.database_file_handler.handle(req).or(Err(e))
            })
        .or_else(|e| {
            // and then try static handler. if all of them fails, return 404
            self.static_handler.handle(req).or(Err(e))
        })
        .or_else(|e| {
            debug!("{}", e.description());
            let err = if let Some(err) = e.error.downcast::<error::Nope>() {
                *err
            } else if e.error.downcast::<NoRoute>().is_some() {
                error::Nope::ResourceNotFound
            } else {
                panic!("all cratesfyi errors should be of type Nope");
            };
            Self::chain(err).handle(req)
        })
    }
}

fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option<String> {

    // version is an Option<&str> from router::Router::get
    // need to decode first
    use url::percent_encoding::percent_decode;
    let req_version = version.and_then(|v| {
        match percent_decode(v.as_bytes()).decode_utf8() {
            Ok(p) => Some(p.into_owned()),
            Err(_) => None,
        }
    }).map(|v| if v == "newest" { "*".to_owned() } else { v })
    .unwrap_or("*".to_string());

    let versions = {
        let mut versions = Vec::new();
        let rows = conn.query("SELECT versions FROM crates WHERE name = $1", &[&name]).unwrap();
        if rows.len() == 0 {
            return None;
        }
        let versions_json: Json = rows.get(0).get(0);
        for version in versions_json.as_array().unwrap() {
            let version: String = version.as_string().unwrap().to_owned();
            versions.push(version);
        }

        versions
    };

    // first check for exact match
    // we can't expect users to use semver in query
    for version in &versions {
        if version == &req_version {
            return Some(version.clone())
        }
    }

    // Now try to match with semver
    let req_sem_ver = match VersionReq::parse(&req_version) {
        Ok(v) => v,
        Err(_) => return None,
    };;

    // we need to sort versions first
    let versions_sem = {
        let mut versions_sem: Vec<Version> = Vec::new();

        for version in &versions {
            // in theory a crate must always have a semver compatible version
            // but check result just in case
            let version = match Version::parse(&version) {
                Ok(v) => v,
                Err(_) => return None,
            };
            versions_sem.push(version);
        }

        versions_sem.sort();
        versions_sem.reverse();
        versions_sem
    };

    for version in &versions_sem {
        if req_sem_ver.matches(&version) {
            return Some(format!("{}", version))
        }
    }

    None
}

/// Wrapper around the pulldown-cmark parser and renderer to render markdown
fn render_markdown(text: &str) -> String {
    use hoedown::{Markdown, Html, Render, Extension};
    use hoedown::renderer::html;

    let extensions = {
        use hoedown::{FENCED_CODE, FOOTNOTES, SUPERSCRIPT, TABLES, AUTOLINK, NO_INTRA_EMPHASIS};

        let mut extensions = Extension::empty();
        extensions.insert(FENCED_CODE);
        extensions.insert(FOOTNOTES);
        extensions.insert(SUPERSCRIPT);
        extensions.insert(TABLES);
        extensions.insert(AUTOLINK);
        extensions.insert(NO_INTRA_EMPHASIS);

        extensions
    };

    let doc = Markdown::new(text).extensions(extensions);
    let mut html = Html::new(html::Flags::empty(), 0);
    html.render(&doc).to_str().unwrap().to_owned()
}

/// Starts cratesfyi web server
pub fn start_web_server(sock_addr: Option<&str>) {
    let cratesfyi = CratesfyiHandler::new();
    Iron::new(cratesfyi).http(sock_addr.unwrap_or("localhost:3000")).unwrap();
}

/// Converts Timespec to nice readable relative time string
fn duration_to_str(ts: time::Timespec) -> String {

    let tm = time::at(ts);
    let delta = time::now() - tm;

    if delta.num_days() > 5 {
        format!("{}", tm.strftime("%b %d, %Y").unwrap())
    } else if delta.num_days() > 1 {
        format!("{} days ago", delta.num_days())
    } else if delta.num_days() == 1 {
        "one day ago".to_string()
    } else if delta.num_hours() > 1 {
        format!("{} hours ago", delta.num_hours())
    } else if delta.num_hours() == 1 {
        "an hour ago".to_string()
    } else if delta.num_minutes() > 1 {
        format!("{} minutes ago", delta.num_minutes())
    } else if delta.num_minutes() == 1 {
        "one minute ago".to_string()
    } else if delta.num_seconds() > 0 {
        format!("{} seconds ago", delta.num_seconds())
    } else {
        "just now".to_string()
    }

}

fn style_css_handler(_: &mut Request) -> IronResult<Response> {
    use iron::headers::{CacheControl, CacheDirective, ContentType};
    let mut response = Response::with((status::Ok, STYLE_CSS));
    let cache = vec![CacheDirective::Public,
    CacheDirective::MaxAge(STATIC_FILE_CACHE_DURATION as u32)];
    response.headers.set(ContentType("text/css".parse().unwrap()));
    response.headers.set(CacheControl(cache));
    Ok(response)
}

/// MetaData used in header
#[derive(Debug)]
pub struct MetaData {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub target_name: Option<String>,
    pub rustdoc_status: bool,
}

impl MetaData {
    pub fn from_crate(conn: &Connection, name: &str, version: &str) -> Option<MetaData> {
        for row in &conn.query("SELECT crates.name,
                                       releases.version,
                                       releases.description,
                                       releases.target_name,
                                       releases.rustdoc_status
                                FROM releases
                                INNER JOIN crates ON crates.id = releases.crate_id
                                WHERE crates.name = $1 AND releases.version = $2",
                                &[&name, &version]).unwrap() {

            return Some(MetaData {
                name: row.get(0),
                version: row.get(1),
                description: row.get(2),
                target_name: row.get(3),
                rustdoc_status: row.get(4),
            });
        }

        None
    }
}

impl ToJson for MetaData {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_owned(), self.name.to_json());
        m.insert("version".to_owned(), self.version.to_json());
        m.insert("description".to_owned(), self.description.to_json());
        m.insert("target_name".to_owned(), self.target_name.to_json());
        m.insert("rustdoc_status".to_owned(), self.rustdoc_status.to_json());
        m.to_json()
    }
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::*;

    #[test]
    #[ignore]
    fn test_start_web_server() {
        // FIXME: This test is doing nothing
        let _ = env_logger::init();
        start_web_server(None);
    }
}
<commit_msg>Return latest version if requested version is any<commit_after>//! Web interface of cratesfyi

pub mod page;

/// ctry! (cratesfyitry) is extremely similar to try! and itry!
/// except it returns an error page response instead of plain Err.
macro_rules! ctry {
    ($result:expr) => (match $result {
        Ok(v) => v,
        Err(e) => {
            return super::page::Page::new(format!("{:?}", e)).title("An error has occured")
                .set_status(::iron::status::BadRequest).to_resp("resp");
        }
    })
}

/// cexpect will check an option and if it's not Some
/// it will return an error page response
macro_rules! cexpect {
    ($option:expr) => (match $option {
        Some(v) => v,
        None => {
            return super::page::Page::new("Resource not found".to_owned())
                .title("An error has occured")
                .set_status(::iron::status::BadRequest).to_resp("resp");
        }
    })
}

/// Gets an extension from Request
macro_rules! extension {
    ($req:expr, $ext:ty) => (
        cexpect!($req.extensions.get::<$ext>())
        )
}

mod rustdoc;
mod releases;
mod crate_details;
mod source;
mod pool;
mod file;
mod builds;
mod error;
mod sitemap;

use std::env;
use std::error::Error;
use std::time::Duration;
use std::path::PathBuf;
use iron::prelude::*;
use iron::{Handler, status};
use router::{Router, NoRoute};
use staticfile::Static;
use handlebars_iron::{HandlebarsEngine, DirectorySource};
use time;
use postgres::Connection;
use semver::{Version, VersionReq};
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;

/// Duration of static files for staticfile and DatabaseFileHandler (in seconds)
const STATIC_FILE_CACHE_DURATION: u64 = 60 * 60 * 24 * 30 * 12;   // 12 months
const STYLE_CSS: &'static str = include_str!(concat!(env!("OUT_DIR"), "/style.css"));

struct CratesfyiHandler {
    router_handler: Box<Handler>,
    database_file_handler: Box<Handler>,
    static_handler: Box<Handler>,
}

impl CratesfyiHandler {
    fn chain<H: Handler>(base: H) -> Chain {
        // TODO: Use DocBuilderOptions for paths
        let mut hbse = HandlebarsEngine::new();
        hbse.add(Box::new(DirectorySource::new("./templates", ".hbs")));

        // load templates
        if let Err(e) = hbse.reload() {
            panic!("Failed to load handlebar templates: {}", e.description());
        }

        let mut chain = Chain::new(base);
        chain.link_before(pool::Pool::new());
        chain.link_after(hbse);
        chain
    }

    pub fn new() -> CratesfyiHandler {
        let mut router = Router::new();
        router.get("/", releases::home_page);
        router.get("/style.css", style_css_handler);
        router.get("/about", |_: &mut Request| {
            page::Page::new(false).title("About Docs.rs").to_resp("about")
        });
        router.get("/robots.txt", sitemap::robots_txt_handler);
        router.get("/sitemap.xml", sitemap::sitemap_handler);
        router.get("/releases", releases::releases_handler);
        router.get("/releases/feed", releases::releases_feed_handler);
        router.get("/releases/recent/:page", releases::releases_handler);
        router.get("/releases/stars", releases::stars_handler);
        router.get("/releases/stars/:page", releases::stars_handler);
        router.get("/releases/:author", releases::author_handler);
        router.get("/releases/:author/:page", releases::author_handler);
        router.get("/releases/activity", releases::activity_handler);
        router.get("/releases/search", releases::search_handler);
        router.get("/crate/:name", crate_details::crate_details_handler);
        router.get("/crate/:name/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/", crate_details::crate_details_handler);
        router.get("/crate/:name/:version/builds", builds::build_list_handler);
        router.get("/crate/:name/:version/builds/:id", builds::build_list_handler);
        router.get("/crate/:name/:version/source/", source::source_browser_handler);
        router.get("/crate/:name/:version/source/*", source::source_browser_handler);
        router.get("/:crate", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/badge.svg", rustdoc::badge_handler);
        router.get("/:crate/:version", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/search-index.js", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target", rustdoc::rustdoc_redirector_handler);
        router.get("/:crate/:version/:target/", rustdoc::rustdoc_html_server_handler);
        router.get("/:crate/:version/:target/*.html", rustdoc::rustdoc_html_server_handler);

        let router_chain = Self::chain(router);
        let prefix = PathBuf::from(env::var("CRATESFYI_PREFIX").unwrap()).join("public_html");
        let static_handler = Static::new(prefix)
            .cache(Duration::from_secs(STATIC_FILE_CACHE_DURATION));

        CratesfyiHandler {
            router_handler: Box::new(router_chain),
            database_file_handler: Box::new(file::DatabaseFileHandler),
            static_handler: Box::new(static_handler),
        }
    }
}

impl Handler for CratesfyiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        // try router first then db/static file handler
        // return 404 if none of them return Ok
        self.router_handler
            .handle(req)
            .or_else(|e| {
                // if router fails try to serve files from database first
                self.database_file_handler.handle(req).or(Err(e))
            })
        .or_else(|e| {
            // and then try static handler. if all of them fails, return 404
            self.static_handler.handle(req).or(Err(e))
        })
        .or_else(|e| {
            debug!("{}", e.description());
            let err = if let Some(err) = e.error.downcast::<error::Nope>() {
                *err
            } else if e.error.downcast::<NoRoute>().is_some() {
                error::Nope::ResourceNotFound
            } else {
                panic!("all cratesfyi errors should be of type Nope");
            };
            Self::chain(err).handle(req)
        })
    }
}

fn match_version(conn: &Connection, name: &str, version: Option<&str>) -> Option<String> {

    // version is an Option<&str> from router::Router::get
    // need to decode first
    use url::percent_encoding::percent_decode;
    let req_version = version.and_then(|v| {
        match percent_decode(v.as_bytes()).decode_utf8() {
            Ok(p) => Some(p.into_owned()),
            Err(_) => None,
        }
    }).map(|v| if v == "newest" { "*".to_owned() } else { v })
    .unwrap_or("*".to_string());

    let versions = {
        let mut versions = Vec::new();
        let rows = conn.query("SELECT versions FROM crates WHERE name = $1", &[&name]).unwrap();
        if rows.len() == 0 {
            return None;
        }
        let versions_json: Json = rows.get(0).get(0);
        for version in versions_json.as_array().unwrap() {
            let version: String = version.as_string().unwrap().to_owned();
            versions.push(version);
        }

        versions
    };

    // first check for exact match
    // we can't expect users to use semver in query
    for version in &versions {
        if version == &req_version {
            return Some(version.clone())
        }
    }

    // Now try to match with semver
    let req_sem_ver = match VersionReq::parse(&req_version) {
        Ok(v) => v,
        Err(_) => return None,
    };;

    // we need to sort versions first
    let versions_sem = {
        let mut versions_sem: Vec<Version> = Vec::new();

        for version in &versions {
            // in theory a crate must always have a semver compatible version
            // but check result just in case
            let version = match Version::parse(&version) {
                Ok(v) => v,
                Err(_) => return None,
            };
            versions_sem.push(version);
        }

        versions_sem.sort();
        versions_sem.reverse();
        versions_sem
    };

    // semver is acting weird for '*' (any) range if a crate only have pre-release versions
    // return first version if requested version is '*'
    if req_version == "*" && !versions_sem.is_empty() {
        return Some(format!("{}", versions_sem[0]));
    }

    for version in &versions_sem {
        if req_sem_ver.matches(&version) {
            return Some(format!("{}", version))
        }
    }

    None
}

/// Wrapper around the pulldown-cmark parser and renderer to render markdown
fn render_markdown(text: &str) -> String {
    use hoedown::{Markdown, Html, Render, Extension};
    use hoedown::renderer::html;

    let extensions = {
        use hoedown::{FENCED_CODE, FOOTNOTES, SUPERSCRIPT, TABLES, AUTOLINK, NO_INTRA_EMPHASIS};

        let mut extensions = Extension::empty();
        extensions.insert(FENCED_CODE);
        extensions.insert(FOOTNOTES);
        extensions.insert(SUPERSCRIPT);
        extensions.insert(TABLES);
        extensions.insert(AUTOLINK);
        extensions.insert(NO_INTRA_EMPHASIS);

        extensions
    };

    let doc = Markdown::new(text).extensions(extensions);
    let mut html = Html::new(html::Flags::empty(), 0);
    html.render(&doc).to_str().unwrap().to_owned()
}

/// Starts cratesfyi web server
pub fn start_web_server(sock_addr: Option<&str>) {
    let cratesfyi = CratesfyiHandler::new();
    Iron::new(cratesfyi).http(sock_addr.unwrap_or("localhost:3000")).unwrap();
}

/// Converts Timespec to nice readable relative time string
fn duration_to_str(ts: time::Timespec) -> String {

    let tm = time::at(ts);
    let delta = time::now() - tm;

    if delta.num_days() > 5 {
        format!("{}", tm.strftime("%b %d, %Y").unwrap())
    } else if delta.num_days() > 1 {
        format!("{} days ago", delta.num_days())
    } else if delta.num_days() == 1 {
        "one day ago".to_string()
    } else if delta.num_hours() > 1 {
        format!("{} hours ago", delta.num_hours())
    } else if delta.num_hours() == 1 {
        "an hour ago".to_string()
    } else if delta.num_minutes() > 1 {
        format!("{} minutes ago", delta.num_minutes())
    } else if delta.num_minutes() == 1 {
        "one minute ago".to_string()
    } else if delta.num_seconds() > 0 {
        format!("{} seconds ago", delta.num_seconds())
    } else {
        "just now".to_string()
    }

}

fn style_css_handler(_: &mut Request) -> IronResult<Response> {
    use iron::headers::{CacheControl, CacheDirective, ContentType};
    let mut response = Response::with((status::Ok, STYLE_CSS));
    let cache = vec![CacheDirective::Public,
    CacheDirective::MaxAge(STATIC_FILE_CACHE_DURATION as u32)];
    response.headers.set(ContentType("text/css".parse().unwrap()));
    response.headers.set(CacheControl(cache));
    Ok(response)
}

/// MetaData used in header
#[derive(Debug)]
pub struct MetaData {
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub target_name: Option<String>,
    pub rustdoc_status: bool,
}

impl MetaData {
    pub fn from_crate(conn: &Connection, name: &str, version: &str) -> Option<MetaData> {
        for row in &conn.query("SELECT crates.name,
                                       releases.version,
                                       releases.description,
                                       releases.target_name,
                                       releases.rustdoc_status
                                FROM releases
                                INNER JOIN crates ON crates.id = releases.crate_id
                                WHERE crates.name = $1 AND releases.version = $2",
                                &[&name, &version]).unwrap() {

            return Some(MetaData {
                name: row.get(0),
                version: row.get(1),
                description: row.get(2),
                target_name: row.get(3),
                rustdoc_status: row.get(4),
            });
        }

        None
    }
}

impl ToJson for MetaData {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("name".to_owned(), self.name.to_json());
        m.insert("version".to_owned(), self.version.to_json());
        m.insert("description".to_owned(), self.description.to_json());
        m.insert("target_name".to_owned(), self.target_name.to_json());
        m.insert("rustdoc_status".to_owned(), self.rustdoc_status.to_json());
        m.to_json()
    }
}

#[cfg(test)]
mod test {
    extern crate env_logger;
    use super::*;

    #[test]
    #[ignore]
    fn test_start_web_server() {
        // FIXME: This test is doing nothing
        let _ = env_logger::init();
        start_web_server(None);
    }
}
<|endoftext|>


<commit_before>use parser::ast::Ast;
use parser::ast::Elem::ElemFunction;
use parser::ast::Expr::{self, ExprUn, ExprBin, ExprLitInt, ExprLitStr, ExprLitBool,
                ExprAssign, ExprIdent};
use parser::ast::ExprUnType;
use parser::ast::ExprBinType;
use parser::ast::ExprLitIntType;
use parser::ast::ExprLitStrType;
use parser::ast::ExprLitBoolType;
use parser::ast::ExprIdentType;
use parser::ast::ExprAssignType;
use parser::ast::Function;
use parser::ast::Param;
use parser::ast::Stmt::{self, StmtBlock, StmtBreak, StmtContinue, StmtExpr,
                StmtIf, StmtLoop, StmtReturn, StmtVar, StmtWhile};
use parser::ast::StmtBlockType;
use parser::ast::StmtBreakType;
use parser::ast::StmtContinueType;
use parser::ast::StmtExprType;
use parser::ast::StmtIfType;
use parser::ast::StmtLoopType;
use parser::ast::StmtReturnType;
use parser::ast::StmtVarType;
use parser::ast::StmtWhileType;
use parser::ast::Type;
use parser::interner::{Interner, Name};

macro_rules! dump {
    ($self_:ident, $($x:expr),*) => {{
        for _ in 0..($self_.indent*2) {
            print!(" ");
        }

        println!($($x,)*);
    }};
}

pub fn dump<'a>(ast: &'a Ast, interner: &'a Interner) {
    AstDumper::new(ast, interner).dump();
}

struct AstDumper<'a> {
    ast: &'a Ast,
    interner: &'a Interner,
    indent: u32,
}

impl<'a> AstDumper<'a> {
    fn new<'b>(ast: &'b Ast, interner: &'b Interner) -> AstDumper<'b> {
        AstDumper {
            ast: ast,
            interner: interner,
            indent: 0
        }
    }

    fn dump(&mut self) {
        for el in &self.ast.elements {
            match *el {
                ElemFunction(ref fct) => self.dump_fct(fct),
                _ => unreachable!()
            }
        }
    }

    fn dump_fct(&mut self, fct: &Function) {
        dump!(self, "fct {} @ {} {}", self.str(fct.name), fct.pos, fct.id);

        self.indent(|d| {
            dump!(d, "params");
            d.indent(|d| {
                if fct.params.is_empty() {
                    dump!(d, "no params");
                } else {
                    for param in &fct.params {
                        d.dump_param(&param);
                    }
                }
            });

            dump!(d, "returns");

            if let Some(ref ty) = fct.return_type {
                d.indent(|d| d.dump_type(ty));
            } else {
                d.indent(|d| dump!(d, "<no return type>"))
            }

            dump!(d, "executes");
            d.indent(|d| d.dump_stmt(&fct.block));
        });
    }

    fn dump_param(&mut self, param: &Param) {
        dump!(self, "param {} @ {} {}", self.str(param.name), param.pos, param.id);
        self.indent(|d| d.dump_type(&param.data_type));
    }

    fn dump_type(&mut self, ty: &Type) {
        dump!(self, "type `{}` @ {:?} {}", ty.to_string(self.interner), ty.pos(), ty.id());
    }

    fn dump_stmt(&mut self, stmt: &Stmt) {
        match *stmt {
            StmtBlock(ref block) => self.dump_stmt_block(block),
            StmtReturn(ref ret) => self.dump_stmt_return(ret),
            StmtBreak(ref stmt) => self.dump_stmt_break(stmt),
            StmtContinue(ref stmt) => self.dump_stmt_continue(stmt),
            StmtExpr(ref expr) => self.dump_stmt_expr(expr),
            StmtIf(ref stmt) => self.dump_stmt_if(stmt),
            StmtVar(ref stmt) => self.dump_stmt_var(stmt),
            StmtWhile(ref stmt) => self.dump_stmt_while(stmt),
            StmtLoop(ref stmt) => self.dump_stmt_loop(stmt),
        }
    }

    fn dump_stmt_var(&mut self, stmt: &StmtVarType) {
        dump!(self, "var {} @ {} {}", self.str(stmt.name), stmt.pos, stmt.id);

        self.indent(|d| {
            dump!(d, "type");
            d.indent(|d| {
                if let Some(ref ty) = stmt.data_type {
                    d.dump_type(ty);
                } else {
                    dump!(d, "<no type given>");
                }
            });

            dump!(d, "expr");
            d.indent(|d| {
                if let Some(ref expr) = stmt.expr {
                    d.dump_expr(expr);
                } else {
                    dump!(d, "<no expr given>");
                }
            });
        });
    }

    fn dump_stmt_while(&mut self, stmt: &StmtWhileType) {
        dump!(self, "while @ {} {}", stmt.pos, stmt.id);

        self.indent(|d| {
            dump!(d, "cond");
            d.indent(|d| { d.dump_expr(&stmt.cond); });

            dump!(d, "body");
            d.indent(|d| { d.dump_stmt(&stmt.block); });
        });
    }

    fn dump_stmt_loop(&mut self, stmt: &StmtLoopType) {
        dump!(self, "loop @ {} {}", stmt.pos, stmt.id);
        self.indent(|d| { d.dump_stmt(&stmt.block); });
    }

    fn dump_stmt_if(&mut self, stmt: &StmtIfType) {
        dump!(self, "if @ {} {}", stmt.pos, stmt.id);

        self.indent(|d| {
            d.indent(|d| { d.dump_expr(&stmt.cond); });
            dump!(d, "then");
            d.indent(|d| { d.dump_stmt(&stmt.then_block); });
            dump!(d, "else");
            d.indent(|d| { d.dump_stmt(&stmt.then_block); });
        });
    }

    fn dump_stmt_expr(&mut self, stmt: &StmtExprType) {
        dump!(self, "expr stmt @ {} {}", stmt.pos, stmt.id);
        self.indent(|d| { d.dump_expr(&stmt.expr); });
    }

    fn dump_stmt_block(&mut self, block: &StmtBlockType) {
        dump!(self, "block ({} statement(s)) @ {} {}",
            block.stmts.len(), block.pos, block.id);

        self.indent(|d| {
            if block.stmts.is_empty() {
                dump!(d, "no statements");
            } else {
                for stmt in &block.stmts {
                    d.dump_stmt(stmt);
                }
            }
        });

        dump!(self, "block end");
    }

    fn dump_stmt_return(&mut self, ret: &StmtReturnType) {
        dump!(self, "return @ {} {}", ret.pos, ret.id);

        self.indent(|d| {
            if let Some(ref expr) = ret.expr {
                d.dump_expr(expr);
            } else {
                dump!(d, "<nothing>");
            }
        });
    }

    fn dump_stmt_break(&mut self, stmt: &StmtBreakType) {
        dump!(self, "break @ {} {}", stmt.pos, stmt.id);
    }

    fn dump_stmt_continue(&mut self, stmt: &StmtContinueType) {
        dump!(self, "continue @ {} {}", stmt.pos, stmt.id);
    }

    fn dump_expr(&mut self, expr: &Expr) {
        match *expr {
            ExprUn(ref un) => self.dump_expr_un(un),
            ExprBin(ref bin) => self.dump_expr_bin(bin),
            ExprLitInt(ref lit) => self.dump_expr_lit_int(lit),
            ExprLitStr(ref lit) => self.dump_expr_lit_str(lit),
            ExprLitBool(ref lit) => self.dump_expr_lit_bool(lit),
            ExprIdent(ref ident) => self.dump_expr_ident(ident),
            ExprAssign(ref assign) => self.dump_expr_assign(assign),
        }
    }

    fn dump_expr_lit_int(&mut self, lit: &ExprLitIntType) {
        dump!(self, "lit int {} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_lit_str(&mut self, lit: &ExprLitStrType) {
        dump!(self, "lit string {:?} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_lit_bool(&mut self, lit: &ExprLitBoolType) {
        dump!(self, "lit bool {} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_ident(&mut self, ident: &ExprIdentType) {
        dump!(self, "ident {} @ {} {}", self.str(ident.name), ident.pos, ident.id);
    }

    fn dump_expr_un(&mut self, expr: &ExprUnType) {
        dump!(self, "unary {:?} @ {} {}", expr.op, expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.opnd));
    }

    fn dump_expr_bin(&mut self, expr: &ExprBinType) {
        self.indent(|d| d.dump_expr(&expr.rhs));
        dump!(self, "binary {:?} @ {} {}", expr.op, expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.lhs));
    }

    fn dump_expr_assign(&mut self, expr: &ExprAssignType) {
        self.indent(|d| d.dump_expr(&expr.rhs));
        dump!(self, "assign (=) @ {} {}", expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.lhs));
    }

    fn indent<F>(&mut self, fct: F) where F: Fn(&mut AstDumper) -> () {
        let old = self.indent;
        self.indent = old+1;

        fct(self);

        self.indent = old;
    }

    fn str(&self, name: Name) -> &str {
        self.interner.str(name)
    }
}

<commit_msg>ast: dumper macro reworked<commit_after>use parser::ast::Ast;
use parser::ast::Elem::ElemFunction;
use parser::ast::Expr::{self, ExprUn, ExprBin, ExprLitInt, ExprLitStr, ExprLitBool,
                ExprAssign, ExprIdent};
use parser::ast::ExprUnType;
use parser::ast::ExprBinType;
use parser::ast::ExprLitIntType;
use parser::ast::ExprLitStrType;
use parser::ast::ExprLitBoolType;
use parser::ast::ExprIdentType;
use parser::ast::ExprAssignType;
use parser::ast::Function;
use parser::ast::Param;
use parser::ast::Stmt::{self, StmtBlock, StmtBreak, StmtContinue, StmtExpr,
                StmtIf, StmtLoop, StmtReturn, StmtVar, StmtWhile};
use parser::ast::StmtBlockType;
use parser::ast::StmtBreakType;
use parser::ast::StmtContinueType;
use parser::ast::StmtExprType;
use parser::ast::StmtIfType;
use parser::ast::StmtLoopType;
use parser::ast::StmtReturnType;
use parser::ast::StmtVarType;
use parser::ast::StmtWhileType;
use parser::ast::Type;
use parser::interner::{Interner, Name};

macro_rules! dump {
    ($self_:ident, $($message:tt)*) => {{
        for _ in 0..($self_.indent*2) {
            print!(" ");
        }

        println!($($message)*);
    }};
}

pub fn dump<'a>(ast: &'a Ast, interner: &'a Interner) {
    AstDumper::new(ast, interner).dump();
}

struct AstDumper<'a> {
    ast: &'a Ast,
    interner: &'a Interner,
    indent: u32,
}

impl<'a> AstDumper<'a> {
    fn new<'b>(ast: &'b Ast, interner: &'b Interner) -> AstDumper<'b> {
        AstDumper {
            ast: ast,
            interner: interner,
            indent: 0
        }
    }

    fn dump(&mut self) {
        for el in &self.ast.elements {
            match *el {
                ElemFunction(ref fct) => self.dump_fct(fct),
                _ => unreachable!()
            }
        }
    }

    fn dump_fct(&mut self, fct: &Function) {
        dump!(self, "fct {} @ {} {}", self.str(fct.name), fct.pos, fct.id);

        self.indent(|d| {
            dump!(d, "params");
            d.indent(|d| {
                if fct.params.is_empty() {
                    dump!(d, "no params");
                } else {
                    for param in &fct.params {
                        d.dump_param(&param);
                    }
                }
            });

            dump!(d, "returns");

            if let Some(ref ty) = fct.return_type {
                d.indent(|d| d.dump_type(ty));
            } else {
                d.indent(|d| dump!(d, "<no return type>"))
            }

            dump!(d, "executes");
            d.indent(|d| d.dump_stmt(&fct.block));
        });
    }

    fn dump_param(&mut self, param: &Param) {
        dump!(self, "param {} @ {} {}", self.str(param.name), param.pos, param.id);
        self.indent(|d| d.dump_type(&param.data_type));
    }

    fn dump_type(&mut self, ty: &Type) {
        dump!(self, "type `{}` @ {:?} {}", ty.to_string(self.interner), ty.pos(), ty.id());
    }

    fn dump_stmt(&mut self, stmt: &Stmt) {
        match *stmt {
            StmtBlock(ref block) => self.dump_stmt_block(block),
            StmtReturn(ref ret) => self.dump_stmt_return(ret),
            StmtBreak(ref stmt) => self.dump_stmt_break(stmt),
            StmtContinue(ref stmt) => self.dump_stmt_continue(stmt),
            StmtExpr(ref expr) => self.dump_stmt_expr(expr),
            StmtIf(ref stmt) => self.dump_stmt_if(stmt),
            StmtVar(ref stmt) => self.dump_stmt_var(stmt),
            StmtWhile(ref stmt) => self.dump_stmt_while(stmt),
            StmtLoop(ref stmt) => self.dump_stmt_loop(stmt),
        }
    }

    fn dump_stmt_var(&mut self, stmt: &StmtVarType) {
        dump!(self, "var {} @ {} {}", self.str(stmt.name), stmt.pos, stmt.id);

        self.indent(|d| {
            dump!(d, "type");
            d.indent(|d| {
                if let Some(ref ty) = stmt.data_type {
                    d.dump_type(ty);
                } else {
                    dump!(d, "<no type given>");
                }
            });

            dump!(d, "expr");
            d.indent(|d| {
                if let Some(ref expr) = stmt.expr {
                    d.dump_expr(expr);
                } else {
                    dump!(d, "<no expr given>");
                }
            });
        });
    }

    fn dump_stmt_while(&mut self, stmt: &StmtWhileType) {
        dump!(self, "while @ {} {}", stmt.pos, stmt.id);

        self.indent(|d| {
            dump!(d, "cond");
            d.indent(|d| { d.dump_expr(&stmt.cond); });

            dump!(d, "body");
            d.indent(|d| { d.dump_stmt(&stmt.block); });
        });
    }

    fn dump_stmt_loop(&mut self, stmt: &StmtLoopType) {
        dump!(self, "loop @ {} {}", stmt.pos, stmt.id);
        self.indent(|d| { d.dump_stmt(&stmt.block); });
    }

    fn dump_stmt_if(&mut self, stmt: &StmtIfType) {
        dump!(self, "if @ {} {}", stmt.pos, stmt.id);

        self.indent(|d| {
            d.indent(|d| { d.dump_expr(&stmt.cond); });
            dump!(d, "then");
            d.indent(|d| { d.dump_stmt(&stmt.then_block); });
            dump!(d, "else");
            d.indent(|d| { d.dump_stmt(&stmt.then_block); });
        });
    }

    fn dump_stmt_expr(&mut self, stmt: &StmtExprType) {
        dump!(self, "expr stmt @ {} {}", stmt.pos, stmt.id);
        self.indent(|d| { d.dump_expr(&stmt.expr); });
    }

    fn dump_stmt_block(&mut self, block: &StmtBlockType) {
        dump!(self, "block ({} statement(s)) @ {} {}",
            block.stmts.len(), block.pos, block.id);

        self.indent(|d| {
            if block.stmts.is_empty() {
                dump!(d, "no statements");
            } else {
                for stmt in &block.stmts {
                    d.dump_stmt(stmt);
                }
            }
        });

        dump!(self, "block end");
    }

    fn dump_stmt_return(&mut self, ret: &StmtReturnType) {
        dump!(self, "return @ {} {}", ret.pos, ret.id);

        self.indent(|d| {
            if let Some(ref expr) = ret.expr {
                d.dump_expr(expr);
            } else {
                dump!(d, "<nothing>");
            }
        });
    }

    fn dump_stmt_break(&mut self, stmt: &StmtBreakType) {
        dump!(self, "break @ {} {}", stmt.pos, stmt.id);
    }

    fn dump_stmt_continue(&mut self, stmt: &StmtContinueType) {
        dump!(self, "continue @ {} {}", stmt.pos, stmt.id);
    }

    fn dump_expr(&mut self, expr: &Expr) {
        match *expr {
            ExprUn(ref un) => self.dump_expr_un(un),
            ExprBin(ref bin) => self.dump_expr_bin(bin),
            ExprLitInt(ref lit) => self.dump_expr_lit_int(lit),
            ExprLitStr(ref lit) => self.dump_expr_lit_str(lit),
            ExprLitBool(ref lit) => self.dump_expr_lit_bool(lit),
            ExprIdent(ref ident) => self.dump_expr_ident(ident),
            ExprAssign(ref assign) => self.dump_expr_assign(assign),
        }
    }

    fn dump_expr_lit_int(&mut self, lit: &ExprLitIntType) {
        dump!(self, "lit int {} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_lit_str(&mut self, lit: &ExprLitStrType) {
        dump!(self, "lit string {:?} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_lit_bool(&mut self, lit: &ExprLitBoolType) {
        dump!(self, "lit bool {} @ {} {}", lit.value, lit.pos, lit.id);
    }

    fn dump_expr_ident(&mut self, ident: &ExprIdentType) {
        dump!(self, "ident {} @ {} {}", self.str(ident.name), ident.pos, ident.id);
    }

    fn dump_expr_un(&mut self, expr: &ExprUnType) {
        dump!(self, "unary {:?} @ {} {}", expr.op, expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.opnd));
    }

    fn dump_expr_bin(&mut self, expr: &ExprBinType) {
        self.indent(|d| d.dump_expr(&expr.rhs));
        dump!(self, "binary {:?} @ {} {}", expr.op, expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.lhs));
    }

    fn dump_expr_assign(&mut self, expr: &ExprAssignType) {
        self.indent(|d| d.dump_expr(&expr.rhs));
        dump!(self, "assign (=) @ {} {}", expr.pos, expr.id);
        self.indent(|d| d.dump_expr(&expr.lhs));
    }

    fn indent<F>(&mut self, fct: F) where F: Fn(&mut AstDumper) -> () {
        let old = self.indent;
        self.indent = old+1;

        fct(self);

        self.indent = old;
    }

    fn str(&self, name: Name) -> &str {
        self.interner.str(name)
    }
}

<|endoftext|>


<commit_before>#![deny(warnings)]
#![feature(asm)]
#![feature(const_fn)]
#![feature(process_try_wait)]

extern crate orbclient;

#[cfg(not(target_os = "redox"))]
extern crate libc;

#[cfg(target_os = "redox")]
extern crate syscall;

use orbclient::event;
use std::{env, str};
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::{self, Result, Read, Write};
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};

use console::Console;
use getpty::getpty;

mod console;
mod getpty;

#[cfg(not(target_os="redox"))]
pub fn before_exec() -> Result<()> {
    use libc;
    unsafe {
        if libc::setsid() < 0 {
            panic!("setsid: {:?}", io::Error::last_os_error());
        }
        if libc::ioctl(0, libc::TIOCSCTTY, 1) < 0 {
            panic!("ioctl: {:?}", io::Error::last_os_error());
        }
    }
    Ok(())
}

#[cfg(target_os="redox")]
pub fn before_exec() -> Result<()> {
    Ok(())
}

#[cfg(target_os = "redox")]
fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) {
    extern crate syscall;

    use std::os::unix::io::AsRawFd;

    let mut event_file = File::open("event:").expect("terminal: failed to open event file");

    let window_fd = console.window.as_raw_fd();
    syscall::fevent(window_fd, syscall::flag::EVENT_READ).expect("terminal: failed to fevent console window");

    let mut master = unsafe { File::from_raw_fd(master_fd) };
    syscall::fevent(master_fd, syscall::flag::EVENT_READ).expect("terminal: failed to fevent master PTY");

    let mut handle_event = |event_id: usize, event_count: usize| -> bool {
        if event_id == window_fd {
            for event in console.window.events() {
                if event.code == event::EVENT_QUIT {
                    return false;
                }

                console.input(&event);
            }

            if ! console.input.is_empty()  {
                if let Err(err) = master.write(&console.input) {
                    let term_stderr = io::stderr();
                    let mut term_stderr = term_stderr.lock();

                    let _ = term_stderr.write(b"failed to write stdin: ");
                    let _ = term_stderr.write(err.description().as_bytes());
                    let _ = term_stderr.write(b"\n");
                    return false;
                }
                let _ = master.flush();
                console.input.clear();
            }
        } else if event_id == master_fd {
            let mut packet = [0; 4096];
            let count = master.read(&mut packet).expect("terminal: failed to read master PTY");
            if count == 0 {
                if event_count == 0 {
                    return false;
                }
            } else {
                console.write(&packet[1..count], true).expect("terminal: failed to write to console");

                //if packet[0] & 1 == 1
                {
                    console.redraw();
                }
            }
        } else {
            println!("Unknown event {}", event_id);
        }

        true
    };

    handle_event(window_fd, 0);
    handle_event(master_fd, 0);

    'events: loop {
        let mut sys_event = syscall::Event::default();
        event_file.read(&mut sys_event).expect("terminal: failed to read event file");
        if ! handle_event(sys_event.id, sys_event.data) {
            break 'events;
        }
    }

    let _ = process.kill();
    process.wait().expect("terminal: failed to wait on shell");
}

#[cfg(not(target_os = "redox"))]
fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) {
    use libc;
    use std::io::ErrorKind;
    use std::thread;
    use std::time::Duration;

    unsafe {
        let size = libc::winsize {
            ws_row: console.console.h as libc::c_ushort,
            ws_col: console.console.w as libc::c_ushort,
            ws_xpixel: 0,
            ws_ypixel: 0
        };
        if libc::ioctl(master_fd, libc::TIOCSWINSZ, &size as *const libc::winsize) < 0 {
            panic!("ioctl: {:?}", io::Error::last_os_error());
        }
    }

    console.console.raw_mode = true;

    let mut master = unsafe { File::from_raw_fd(master_fd) };

    'events: loop {
        for event in console.window.events() {
            if event.code == event::EVENT_QUIT {
                break 'events;
            }

            console.input(&event);
        }

        if ! console.input.is_empty()  {
            if let Err(err) = master.write(&console.input) {
                let term_stderr = io::stderr();
                let mut term_stderr = term_stderr.lock();

                let _ = term_stderr.write(b"failed to write stdin: ");
                let _ = term_stderr.write(err.description().as_bytes());
                let _ = term_stderr.write(b"\n");
                break 'events;
            }
            let _ = master.flush();
            console.input.clear();
        }

        let mut packet = [0; 4096];
        match master.read(&mut packet) {
            Ok(0) => break 'events,
            Ok(count) => {
                console.write(&packet[..count], true).expect("terminal: failed to write to console");
                console.redraw();
            },
            Err(err) => match err.kind() {
                ErrorKind::WouldBlock => (),
                _ => panic!("terminal: failed to read master PTY: {:?}", err)
            }
        }

        match process.try_wait() {
            Ok(_status) => break 'events,
            Err(err) => match err.kind() {
                ErrorKind::WouldBlock => (),
                _ => panic!("terminal: failed to wait on child: {:?}", err)
            }
        }

        thread::sleep(Duration::new(0, 100));
    }

    let _ = process.kill();
    process.wait().expect("terminal: failed to wait on shell");
}

fn main() {
    let shell = env::args().nth(1).unwrap_or("sh".to_string());

    let (master_fd, tty_path) = getpty();

    let slave_stdin = OpenOptions::new().read(true).write(false).open(&tty_path).unwrap();
    let slave_stdout = OpenOptions::new().read(false).write(true).open(&tty_path).unwrap();
    let slave_stderr = OpenOptions::new().read(false).write(true).open(&tty_path).unwrap();

    let width = 800;
    let height = 600;

    env::set_var("COLUMNS", format!("{}", width / 8));
    env::set_var("LINES", format!("{}", height / 16));
    env::set_var("TERM", "linux");
    env::set_var("TTY", format!("{}", tty_path.display()));

    match unsafe {
        Command::new(&shell)
            .stdin(Stdio::from_raw_fd(slave_stdin.into_raw_fd()))
            .stdout(Stdio::from_raw_fd(slave_stdout.into_raw_fd()))
            .stderr(Stdio::from_raw_fd(slave_stderr.into_raw_fd()))
            .before_exec(|| {
                before_exec()
            })
            .spawn()
    } {
        Ok(mut process) => {
            let mut console = Console::new(width, height);
            handle(&mut console, master_fd, &mut process);
        },
        Err(err) => {
            let term_stderr = io::stderr();
            let mut term_stderr = term_stderr.lock();
            let _ = term_stderr.write(b"failed to execute '");
            let _ = term_stderr.write(shell.as_bytes());
            let _ = term_stderr.write(b"': ");
            let _ = term_stderr.write(err.description().as_bytes());
            let _ = term_stderr.write(b"\n");
        }
    }
}
<commit_msg>process_try_wait only on Linux<commit_after>#![deny(warnings)]
#![feature(asm)]
#![feature(const_fn)]
#![cfg_attr(not(target_os = "redox"), feature(process_try_wait))]

extern crate orbclient;

#[cfg(not(target_os = "redox"))]
extern crate libc;

#[cfg(target_os = "redox")]
extern crate syscall;

use orbclient::event;
use std::{env, str};
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::{self, Result, Read, Write};
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};

use console::Console;
use getpty::getpty;

mod console;
mod getpty;

#[cfg(not(target_os="redox"))]
pub fn before_exec() -> Result<()> {
    use libc;
    unsafe {
        if libc::setsid() < 0 {
            panic!("setsid: {:?}", io::Error::last_os_error());
        }
        if libc::ioctl(0, libc::TIOCSCTTY, 1) < 0 {
            panic!("ioctl: {:?}", io::Error::last_os_error());
        }
    }
    Ok(())
}

#[cfg(target_os="redox")]
pub fn before_exec() -> Result<()> {
    Ok(())
}

#[cfg(target_os = "redox")]
fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) {
    extern crate syscall;

    use std::os::unix::io::AsRawFd;

    let mut event_file = File::open("event:").expect("terminal: failed to open event file");

    let window_fd = console.window.as_raw_fd();
    syscall::fevent(window_fd, syscall::flag::EVENT_READ).expect("terminal: failed to fevent console window");

    let mut master = unsafe { File::from_raw_fd(master_fd) };
    syscall::fevent(master_fd, syscall::flag::EVENT_READ).expect("terminal: failed to fevent master PTY");

    let mut handle_event = |event_id: usize, event_count: usize| -> bool {
        if event_id == window_fd {
            for event in console.window.events() {
                if event.code == event::EVENT_QUIT {
                    return false;
                }

                console.input(&event);
            }

            if ! console.input.is_empty()  {
                if let Err(err) = master.write(&console.input) {
                    let term_stderr = io::stderr();
                    let mut term_stderr = term_stderr.lock();

                    let _ = term_stderr.write(b"failed to write stdin: ");
                    let _ = term_stderr.write(err.description().as_bytes());
                    let _ = term_stderr.write(b"\n");
                    return false;
                }
                let _ = master.flush();
                console.input.clear();
            }
        } else if event_id == master_fd {
            let mut packet = [0; 4096];
            let count = master.read(&mut packet).expect("terminal: failed to read master PTY");
            if count == 0 {
                if event_count == 0 {
                    return false;
                }
            } else {
                console.write(&packet[1..count], true).expect("terminal: failed to write to console");

                //if packet[0] & 1 == 1
                {
                    console.redraw();
                }
            }
        } else {
            println!("Unknown event {}", event_id);
        }

        true
    };

    handle_event(window_fd, 0);
    handle_event(master_fd, 0);

    'events: loop {
        let mut sys_event = syscall::Event::default();
        event_file.read(&mut sys_event).expect("terminal: failed to read event file");
        if ! handle_event(sys_event.id, sys_event.data) {
            break 'events;
        }
    }

    let _ = process.kill();
    process.wait().expect("terminal: failed to wait on shell");
}

#[cfg(not(target_os = "redox"))]
fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) {
    use libc;
    use std::io::ErrorKind;
    use std::thread;
    use std::time::Duration;

    unsafe {
        let size = libc::winsize {
            ws_row: console.console.h as libc::c_ushort,
            ws_col: console.console.w as libc::c_ushort,
            ws_xpixel: 0,
            ws_ypixel: 0
        };
        if libc::ioctl(master_fd, libc::TIOCSWINSZ, &size as *const libc::winsize) < 0 {
            panic!("ioctl: {:?}", io::Error::last_os_error());
        }
    }

    console.console.raw_mode = true;

    let mut master = unsafe { File::from_raw_fd(master_fd) };

    'events: loop {
        for event in console.window.events() {
            if event.code == event::EVENT_QUIT {
                break 'events;
            }

            console.input(&event);
        }

        if ! console.input.is_empty()  {
            if let Err(err) = master.write(&console.input) {
                let term_stderr = io::stderr();
                let mut term_stderr = term_stderr.lock();

                let _ = term_stderr.write(b"failed to write stdin: ");
                let _ = term_stderr.write(err.description().as_bytes());
                let _ = term_stderr.write(b"\n");
                break 'events;
            }
            let _ = master.flush();
            console.input.clear();
        }

        let mut packet = [0; 4096];
        match master.read(&mut packet) {
            Ok(0) => break 'events,
            Ok(count) => {
                console.write(&packet[..count], true).expect("terminal: failed to write to console");
                console.redraw();
            },
            Err(err) => match err.kind() {
                ErrorKind::WouldBlock => (),
                _ => panic!("terminal: failed to read master PTY: {:?}", err)
            }
        }

        match process.try_wait() {
            Ok(_status) => break 'events,
            Err(err) => match err.kind() {
                ErrorKind::WouldBlock => (),
                _ => panic!("terminal: failed to wait on child: {:?}", err)
            }
        }

        thread::sleep(Duration::new(0, 100));
    }

    let _ = process.kill();
    process.wait().expect("terminal: failed to wait on shell");
}

fn main() {
    let shell = env::args().nth(1).unwrap_or("sh".to_string());

    let (master_fd, tty_path) = getpty();

    let slave_stdin = OpenOptions::new().read(true).write(false).open(&tty_path).unwrap();
    let slave_stdout = OpenOptions::new().read(false).write(true).open(&tty_path).unwrap();
    let slave_stderr = OpenOptions::new().read(false).write(true).open(&tty_path).unwrap();

    let width = 800;
    let height = 600;

    env::set_var("COLUMNS", format!("{}", width / 8));
    env::set_var("LINES", format!("{}", height / 16));
    env::set_var("TERM", "linux");
    env::set_var("TTY", format!("{}", tty_path.display()));

    match unsafe {
        Command::new(&shell)
            .stdin(Stdio::from_raw_fd(slave_stdin.into_raw_fd()))
            .stdout(Stdio::from_raw_fd(slave_stdout.into_raw_fd()))
            .stderr(Stdio::from_raw_fd(slave_stderr.into_raw_fd()))
            .before_exec(|| {
                before_exec()
            })
            .spawn()
    } {
        Ok(mut process) => {
            let mut console = Console::new(width, height);
            handle(&mut console, master_fd, &mut process);
        },
        Err(err) => {
            let term_stderr = io::stderr();
            let mut term_stderr = term_stderr.lock();
            let _ = term_stderr.write(b"failed to execute '");
            let _ = term_stderr.write(shell.as_bytes());
            let _ = term_stderr.write(b"': ");
            let _ = term_stderr.write(err.description().as_bytes());
            let _ = term_stderr.write(b"\n");
        }
    }
}
<|endoftext|>


<commit_before>//! Get the current size of the terminal
//!
//! NOTE: This is a copy and adaptation of the original mod `size` from the crate `termion`.
//!
//! The original `termion::terminal_size` function only checks the size against `STDOUT`,
//! but to interact with the user we use a custom tty (`/dev/tty` to be precise), so we need
//! to use a different file descriptor.
use libc::{c_int, c_ushort};
use std::io;

#[cfg(target_os = "linux")]
pub const TIOCGWINSZ: usize = 0x0000_5413;

#[cfg(not(target_os = "linux"))]
pub const TIOCGWINSZ: usize = 0x40087468;

#[repr(C)]
struct TermSize {
    row: c_ushort,
    col: c_ushort,
    _x: c_ushort,
    _y: c_ushort,
}

#[cfg(target_env = "musl")]
fn tiocgwinsz() -> i32 {
    TIOCGWINSZ as i32
}

#[cfg(all(not(target_env = "musl"), target_pointer_width = "64"))]
fn tiocgwinsz() -> u64 {
    TIOCGWINSZ as u64
}

#[cfg(all(not(target_env = "musl"), target_pointer_width = "32"))]
fn tiocgwinsz() -> u32 {
    TIOCGWINSZ as u32
}

/// Get the size of the terminal for the given file descriptor
pub fn terminal_size(fileno: c_int) -> io::Result<(u16, u16)> {
    use libc::ioctl;
    use std::mem;

    unsafe {
        let mut size: TermSize = mem::zeroed();

        if ioctl(fileno, tiocgwinsz(), &mut size as *mut _) == 0 {
            Ok((size.col as u16, size.row as u16))
        } else {
            Err(io::Error::new(
                io::ErrorKind::Other,
                "Unable to get the terminal size.",
            ))
        }
    }
}

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

    #[test]
    fn test_size() {
        use libc::STDOUT_FILENO;

        assert!(terminal_size(STDOUT_FILENO).is_ok());
    }
}
<commit_msg>Remove test for terminal_size<commit_after>//! Get the current size of the terminal
//!
//! NOTE: This is a copy and adaptation of the original mod `size` from the crate `termion`.
//!
//! The original `termion::terminal_size` function only checks the size against `STDOUT`,
//! but to interact with the user we use a custom tty (`/dev/tty` to be precise), so we need
//! to use a different file descriptor.
use libc::{c_int, c_ushort};
use std::io;

#[cfg(target_os = "linux")]
pub const TIOCGWINSZ: usize = 0x0000_5413;

#[cfg(not(target_os = "linux"))]
pub const TIOCGWINSZ: usize = 0x40087468;

#[repr(C)]
struct TermSize {
    row: c_ushort,
    col: c_ushort,
    _x: c_ushort,
    _y: c_ushort,
}

#[cfg(target_env = "musl")]
fn tiocgwinsz() -> i32 {
    TIOCGWINSZ as i32
}

#[cfg(all(not(target_env = "musl"), target_pointer_width = "64"))]
fn tiocgwinsz() -> u64 {
    TIOCGWINSZ as u64
}

#[cfg(all(not(target_env = "musl"), target_pointer_width = "32"))]
fn tiocgwinsz() -> u32 {
    TIOCGWINSZ as u32
}

/// Get the size of the terminal for the given file descriptor
pub fn terminal_size(fileno: c_int) -> io::Result<(u16, u16)> {
    use libc::ioctl;
    use std::mem;

    unsafe {
        let mut size: TermSize = mem::zeroed();

        if ioctl(fileno, tiocgwinsz(), &mut size as *mut _) == 0 {
            Ok((size.col as u16, size.row as u16))
        } else {
            Err(io::Error::new(
                io::ErrorKind::Other,
                "Unable to get the terminal size.",
            ))
        }
    }
}
<|endoftext|>