<commit_before>use ::anyhow::Result;
use ::log::error;
use ::std::fs::{remove_file, File};
use ::std::io::prelude::*;
use ::std::path::{Path, PathBuf};

use crate::settings::Settings;

/// Return the paths to temporary stdout and stderr files for a task
pub fn get_log_paths(task_id: usize, settings: &Settings) -> (PathBuf, PathBuf) {
    let pueue_dir = Path::new(&settings.daemon.pueue_directory).join("temp");
    let out_path = pueue_dir.join(format!("{}_stdout.log", task_id));
    let err_path = pueue_dir.join(format!("{}_stderr.log", task_id));
    (out_path, err_path)
}

/// Create and return the file handle for temporary stdout and stderr files for a task
pub fn create_log_file_handles(task_id: usize, settings: &Settings) -> Result<(File, File)> {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    let stdout = File::create(out_path)?;
    let stderr = File::create(err_path)?;

    Ok((stdout, stderr))
}

/// Return the file handle for temporary stdout and stderr files for a task
pub fn get_log_file_handles(task_id: usize, settings: &Settings) -> Result<(File, File)> {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    let stdout = File::open(out_path)?;
    let stderr = File::open(err_path)?;

    Ok((stdout, stderr))
}

/// Return the content of temporary stdout and stderr files for a task
pub fn read_log_files(task_id: usize, settings: &Settings) -> Result<(String, String)> {
    let (mut stdout_handle, mut stderr_handle) = get_log_file_handles(task_id, settings)?;
    let mut stdout = String::new();
    let mut stderr = String::new();

    stdout_handle.read_to_string(&mut stdout)?;
    stderr_handle.read_to_string(&mut stderr)?;

    Ok((stdout, stderr))
}

/// Remove temporary stdout and stderr files for a task
pub fn clean_log_handles(task_id: usize, settings: &Settings) {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    if let Err(err) = remove_file(out_path) {
        error!(
            "Failed to remove stdout file for task {} with error {:?}",
            task_id, err
        );
    };
    if let Err(err) = remove_file(err_path) {
        error!(
            "Failed to remove stderr file for task {} with error {:?}",
            task_id, err
        );
    };
}
<commit_msg>Handle lossy utf-8 from processes<commit_after>use ::anyhow::Result;
use ::log::error;
use ::std::fs::{remove_file, File};
use ::std::io::prelude::*;
use ::std::path::{Path, PathBuf};

use crate::settings::Settings;

/// Return the paths to temporary stdout and stderr files for a task
pub fn get_log_paths(task_id: usize, settings: &Settings) -> (PathBuf, PathBuf) {
    let pueue_dir = Path::new(&settings.daemon.pueue_directory).join("temp");
    let out_path = pueue_dir.join(format!("{}_stdout.log", task_id));
    let err_path = pueue_dir.join(format!("{}_stderr.log", task_id));
    (out_path, err_path)
}

/// Create and return the file handle for temporary stdout and stderr files for a task
pub fn create_log_file_handles(task_id: usize, settings: &Settings) -> Result<(File, File)> {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    let stdout = File::create(out_path)?;
    let stderr = File::create(err_path)?;

    Ok((stdout, stderr))
}

/// Return the file handle for temporary stdout and stderr files for a task
pub fn get_log_file_handles(task_id: usize, settings: &Settings) -> Result<(File, File)> {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    let stdout = File::open(out_path)?;
    let stderr = File::open(err_path)?;

    Ok((stdout, stderr))
}

/// Return the content of temporary stdout and stderr files for a task
pub fn read_log_files(task_id: usize, settings: &Settings) -> Result<(String, String)> {
    let (mut stdout_handle, mut stderr_handle) = get_log_file_handles(task_id, settings)?;
    let mut stdout_buffer = Vec::new();
    let mut stderr_buffer = Vec::new();

    stdout_handle.read_to_end(&mut stdout_buffer)?;
    stderr_handle.read_to_end(&mut stderr_buffer)?;

    let stdout = String::from_utf8_lossy(&stdout_buffer);
    let stderr = String::from_utf8_lossy(&stderr_buffer);

    Ok((stdout.to_string(), stderr.to_string()))
}

/// Remove temporary stdout and stderr files for a task
pub fn clean_log_handles(task_id: usize, settings: &Settings) {
    let (out_path, err_path) = get_log_paths(task_id, settings);
    if let Err(err) = remove_file(out_path) {
        error!(
            "Failed to remove stdout file for task {} with error {:?}",
            task_id, err
        );
    };
    if let Err(err) = remove_file(err_path) {
        error!(
            "Failed to remove stderr file for task {} with error {:?}",
            task_id, err
        );
    };
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Michael Gehring <mg@ebfe.org>
//
//  For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) fname

#[macro_use]
extern crate uucore;

use clap::{App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
use uucore::InvalidEncodingHandling;

// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();

const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "cksum";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";

// this is basically a hack to get "loops" to work on Rust 1.33.  Once we update to Rust 1.46 or
// greater, we can just use while loops
macro_rules! unroll {
    (256, |$i:ident| $s:expr) => {{
        unroll!(@ 32, 0 * 32, $i, $s);
        unroll!(@ 32, 1 * 32, $i, $s);
        unroll!(@ 32, 2 * 32, $i, $s);
        unroll!(@ 32, 3 * 32, $i, $s);
        unroll!(@ 32, 4 * 32, $i, $s);
        unroll!(@ 32, 5 * 32, $i, $s);
        unroll!(@ 32, 6 * 32, $i, $s);
        unroll!(@ 32, 7 * 32, $i, $s);
    }};
    (8, |$i:ident| $s:expr) => {{
        unroll!(@ 8, 0, $i, $s);
    }};

    (@ 32, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 8, $start + 0 * 8, $i, $s);
        unroll!(@ 8, $start + 1 * 8, $i, $s);
        unroll!(@ 8, $start + 2 * 8, $i, $s);
        unroll!(@ 8, $start + 3 * 8, $i, $s);
    }};
    (@ 8, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 4, $start, $i, $s);
        unroll!(@ 4, $start + 4, $i, $s);
    }};
    (@ 4, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 2, $start, $i, $s);
        unroll!(@ 2, $start + 2, $i, $s);
    }};
    (@ 2, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 1, $start, $i, $s);
        unroll!(@ 1, $start + 1, $i, $s);
    }};
    (@ 1, $start:expr, $i:ident, $s:expr) => {{
        let $i = $start;
        let _ = $s;
    }};
}

const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
    let mut table = [0; CRC_TABLE_LEN];

    // NOTE: works on Rust 1.46
    //let mut i = 0;
    //while i < CRC_TABLE_LEN {
    //    table[i] = crc_entry(i as u8) as u32;
    //
    //    i += 1;
    //}
    unroll!(256, |i| {
        table[i] = crc_entry(i as u8) as u32;
    });

    table
}

const fn crc_entry(input: u8) -> u32 {
    let mut crc = (input as u32) << 24;

    // NOTE: this does not work on Rust 1.33, but *does* on 1.46
    //let mut i = 0;
    //while i < 8 {
    //    if crc & 0x8000_0000 != 0 {
    //        crc <<= 1;
    //        crc ^= 0x04c1_1db7;
    //    } else {
    //        crc <<= 1;
    //    }
    //
    //    i += 1;
    //}
    unroll!(8, |_i| {
        let if_cond = crc & 0x8000_0000;
        let if_body = (crc << 1) ^ 0x04c1_1db7;
        let else_body = crc << 1;

        // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
        //       ops
        let cond_table = [else_body, if_body];

        crc = cond_table[(if_cond != 0) as usize];
    });

    crc
}

#[inline]
fn crc_update(crc: u32, input: u8) -> u32 {
    (crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF]
}

#[inline]
fn crc_final(mut crc: u32, mut length: usize) -> u32 {
    while length != 0 {
        crc = crc_update(crc, length as u8);
        length >>= 8;
    }

    !crc
}

fn init_byte_array() -> Vec<u8> {
    vec![0; 1024 * 1024]
}

#[inline]
fn cksum(fname: &str) -> io::Result<(u32, usize)> {
    let mut crc = 0u32;
    let mut size = 0usize;

    let file;
    let mut rd: Box<dyn Read> = match fname {
        "-" => Box::new(stdin()),
        _ => {
            let path = &Path::new(fname);
            if path.is_dir() {
                return Err(std::io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Is a directory",
                ));
            };
            if path.metadata().is_err() {
                return Err(std::io::Error::new(
                    io::ErrorKind::NotFound,
                    "No such file or directory",
                ));
            };
            file = File::open(&path)?;
            Box::new(BufReader::new(file))
        }
    };

    let mut bytes = init_byte_array();
    loop {
        match rd.read(&mut bytes) {
            Ok(num_bytes) => {
                if num_bytes == 0 {
                    return Ok((crc_final(crc, size), size));
                }
                for &b in bytes[..num_bytes].iter() {
                    crc = crc_update(crc, b);
                }
                size += num_bytes;
            }
            Err(err) => return Err(err),
        }
    }
}

mod options {
    pub static FILE: &str = "file";
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    let args = args
        .collect_str(InvalidEncodingHandling::Ignore)
        .accept_any();

    let matches = App::new(executable!())
        .name(NAME)
        .version(VERSION)
        .about(SUMMARY)
        .usage(SYNTAX)
        .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
        .get_matches_from(args);

    let files: Vec<String> = match matches.values_of(options::FILE) {
        Some(v) => v.clone().map(|v| v.to_owned()).collect(),
        None => vec![],
    };

    if files.is_empty() {
        match cksum("-") {
            Ok((crc, size)) => println!("{} {}", crc, size),
            Err(err) => {
                show_error!("{}", err);
                return 2;
            }
        }
        return 0;
    }

    let mut exit_code = 0;
    for fname in &files {
        match cksum(fname.as_ref()) {
            Ok((crc, size)) => println!("{} {} {}", crc, size, fname),
            Err(err) => {
                show_error!("'{}' {}", fname, err);
                exit_code = 2;
            }
        }
    }

    exit_code
}
<commit_msg>refactor/cksum ~ polish spelling (comments, names, and exceptions)<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Michael Gehring <mg@ebfe.org>
//
//  For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) fname

#[macro_use]
extern crate uucore;

use clap::{App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
use uucore::InvalidEncodingHandling;

// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();

const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "cksum";
const SYNTAX: &str = "[OPTIONS] [FILE]...";
const SUMMARY: &str = "Print CRC and size for each file";

// this is basically a hack to get "loops" to work on Rust 1.33.  Once we update to Rust 1.46 or
// greater, we can just use while loops
macro_rules! unroll {
    (256, |$i:ident| $s:expr) => {{
        unroll!(@ 32, 0 * 32, $i, $s);
        unroll!(@ 32, 1 * 32, $i, $s);
        unroll!(@ 32, 2 * 32, $i, $s);
        unroll!(@ 32, 3 * 32, $i, $s);
        unroll!(@ 32, 4 * 32, $i, $s);
        unroll!(@ 32, 5 * 32, $i, $s);
        unroll!(@ 32, 6 * 32, $i, $s);
        unroll!(@ 32, 7 * 32, $i, $s);
    }};
    (8, |$i:ident| $s:expr) => {{
        unroll!(@ 8, 0, $i, $s);
    }};

    (@ 32, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 8, $start + 0 * 8, $i, $s);
        unroll!(@ 8, $start + 1 * 8, $i, $s);
        unroll!(@ 8, $start + 2 * 8, $i, $s);
        unroll!(@ 8, $start + 3 * 8, $i, $s);
    }};
    (@ 8, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 4, $start, $i, $s);
        unroll!(@ 4, $start + 4, $i, $s);
    }};
    (@ 4, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 2, $start, $i, $s);
        unroll!(@ 2, $start + 2, $i, $s);
    }};
    (@ 2, $start:expr, $i:ident, $s:expr) => {{
        unroll!(@ 1, $start, $i, $s);
        unroll!(@ 1, $start + 1, $i, $s);
    }};
    (@ 1, $start:expr, $i:ident, $s:expr) => {{
        let $i = $start;
        let _ = $s;
    }};
}

const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
    let mut table = [0; CRC_TABLE_LEN];

    // NOTE: works on Rust 1.46
    //let mut i = 0;
    //while i < CRC_TABLE_LEN {
    //    table[i] = crc_entry(i as u8) as u32;
    //
    //    i += 1;
    //}
    unroll!(256, |i| {
        table[i] = crc_entry(i as u8) as u32;
    });

    table
}

const fn crc_entry(input: u8) -> u32 {
    let mut crc = (input as u32) << 24;

    // NOTE: this does not work on Rust 1.33, but *does* on 1.46
    //let mut i = 0;
    //while i < 8 {
    //    if crc & 0x8000_0000 != 0 {
    //        crc <<= 1;
    //        crc ^= 0x04c1_1db7;
    //    } else {
    //        crc <<= 1;
    //    }
    //
    //    i += 1;
    //}
    unroll!(8, |_i| {
        let if_condition = crc & 0x8000_0000;
        let if_body = (crc << 1) ^ 0x04c1_1db7;
        let else_body = crc << 1;

        // NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
        //       ops
        let condition_table = [else_body, if_body];

        crc = condition_table[(if_condition != 0) as usize];
    });

    crc
}

#[inline]
fn crc_update(crc: u32, input: u8) -> u32 {
    (crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF]
}

#[inline]
fn crc_final(mut crc: u32, mut length: usize) -> u32 {
    while length != 0 {
        crc = crc_update(crc, length as u8);
        length >>= 8;
    }

    !crc
}

fn init_byte_array() -> Vec<u8> {
    vec![0; 1024 * 1024]
}

#[inline]
fn cksum(fname: &str) -> io::Result<(u32, usize)> {
    let mut crc = 0u32;
    let mut size = 0usize;

    let file;
    let mut rd: Box<dyn Read> = match fname {
        "-" => Box::new(stdin()),
        _ => {
            let path = &Path::new(fname);
            if path.is_dir() {
                return Err(std::io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "Is a directory",
                ));
            };
            if path.metadata().is_err() {
                return Err(std::io::Error::new(
                    io::ErrorKind::NotFound,
                    "No such file or directory",
                ));
            };
            file = File::open(&path)?;
            Box::new(BufReader::new(file))
        }
    };

    let mut bytes = init_byte_array();
    loop {
        match rd.read(&mut bytes) {
            Ok(num_bytes) => {
                if num_bytes == 0 {
                    return Ok((crc_final(crc, size), size));
                }
                for &b in bytes[..num_bytes].iter() {
                    crc = crc_update(crc, b);
                }
                size += num_bytes;
            }
            Err(err) => return Err(err),
        }
    }
}

mod options {
    pub static FILE: &str = "file";
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    let args = args
        .collect_str(InvalidEncodingHandling::Ignore)
        .accept_any();

    let matches = App::new(executable!())
        .name(NAME)
        .version(VERSION)
        .about(SUMMARY)
        .usage(SYNTAX)
        .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
        .get_matches_from(args);

    let files: Vec<String> = match matches.values_of(options::FILE) {
        Some(v) => v.clone().map(|v| v.to_owned()).collect(),
        None => vec![],
    };

    if files.is_empty() {
        match cksum("-") {
            Ok((crc, size)) => println!("{} {}", crc, size),
            Err(err) => {
                show_error!("{}", err);
                return 2;
            }
        }
        return 0;
    }

    let mut exit_code = 0;
    for fname in &files {
        match cksum(fname.as_ref()) {
            Ok((crc, size)) => println!("{} {} {}", crc, size, fname),
            Err(err) => {
                show_error!("'{}' {}", fname, err);
                exit_code = 2;
            }
        }
    }

    exit_code
}
<|endoftext|>


<commit_before>use lookup;
use txt_records::{Spf, Word, Mechanism, Modifier};
use statistics::Statistics;

use ansi_term::Colour;
use chrono::{Local, Duration};
use chrono_humanize::HumanTime;
use error_chain::ChainedError;
use itertools::Itertools;
use std::cmp::Ordering;
use std::io::{self, Write};
use tabwriter::TabWriter;
use trust_dns::rr::{RData, Record};

pub trait OutputModule {
    fn output(&self, w: &mut Write) -> Result<()>;
    fn is_human(&self) -> bool;
}

pub struct DetailsOutput<'a> {
    responses: &'a [lookup::Result<lookup::Response>],
    human: bool,
}

impl<'a> DetailsOutput<'a> {
    pub fn new(responses: &'a [lookup::Result<lookup::Response>], human: bool) -> Self {
        DetailsOutput { responses, human }
    }
}

impl<'a> OutputModule for DetailsOutput<'a> {
    fn output(&self, w: &mut Write) -> Result<()> {
        for response in self.responses {
            match *response {
                Ok(ref r) => write_response(w, r, self.human).chain_err(|| ErrorKind::OutputError)?,
                Err(ref e) => print_error(w, e)?,
            }
        }

        Ok(())
    }

    fn is_human(&self) -> bool {
        self.human
    }
}

pub struct SummaryOutput<'a> {
    statistics: Statistics<'a>,
    human: bool,
}

impl<'a> SummaryOutput<'a> {
    pub fn new(responses: &'a [lookup::Result<lookup::Response>], human: bool) -> Self {
        let statistics = Statistics::from(responses);
        SummaryOutput { statistics, human }
    }
}

impl<'a> OutputModule for SummaryOutput<'a> {
    fn output(&self, w: &mut Write) -> Result<()> {
        writeln!(w, "Received {} (min {}, max {} records) answers from {} servers",
                 self.statistics.num_of_ok_samples,
                 self.statistics.min_num_of_records,
                 self.statistics.max_num_of_records,
                 self.statistics.num_of_samples,
        ).chain_err(|| ErrorKind::OutputError)?;
        let records: Vec<_> = self.statistics
            .record_counts
            .values()
            // TODO: Why do I need to specify a closure and not just a function?
            .sorted_by(|a, b| compare_records(a.0, b.0))
            .iter()
            .map(|rr| {
                let record = rr.0;
                let count = rr.1;
                format!("* {} ({})", fmt_record(record, self.human), count)
            })
            .collect();

        let mut tw = TabWriter::new(vec![]).padding(1);
        write!(&mut tw, "{}", records.join("\n")).chain_err(|| ErrorKind::OutputError)?;
        let out_str = String::from_utf8(tw.into_inner().unwrap()).unwrap();

        writeln!(w, "{}", out_str).chain_err(|| ErrorKind::OutputError)?;

        Ok(())
    }

    fn is_human(&self) -> bool {
        self.human
    }
}

fn write_response(f: &mut Write, r: &lookup::Response, human: bool) -> io::Result<()> {
    if r.answers.is_empty() {
        return writeln!(f, "DNS server {} has no records.", r.server);
    }
    let _ = write!(f, "DNS server {} responded with\n", r.server);
    let answers: Vec<String> = r.answers
        .iter()
        .sorted_by(|a, b| compare_records(a, b))
        .iter()
        .map(|answer|
            format!("* {} [expires {}]",
                    fmt_record(answer, human),
                    if human {
                        humanize_ttl(answer.ttl() as i64)
                    } else {
                        format!("in {} sec", answer.ttl())
                    }
            )
        )
        .collect();

    let mut tw = TabWriter::new(vec![]).padding(1);
    let _ = write!(&mut tw, "{}", answers.join("\n"));
    let out_str = String::from_utf8(tw.into_inner().unwrap()).unwrap();

    writeln!(f, "{}", out_str)
}

fn compare_records(a: &Record, b: &Record) -> Ordering {
    let a = record_type_to_ordinal(a);
    let b = record_type_to_ordinal(b);

    a.cmp(&b)
}

fn record_type_to_ordinal(r: &Record) -> u16 {
    match *r.rdata() {
        RData::SOA(_) => 1000,
        RData::NS(_) => 2000,
        RData::MX(ref mx) => 3000 + mx.preference(),
        RData::TXT(_) => 4000,
        RData::CNAME(_) => 5000,
        RData::A(_) => 6000,
        RData::AAAA(_) => 7000,
        RData::PTR(_) => 8000,
        _ => ::std::u16::MAX,
    }
}

fn humanize_ttl(ttl: i64) -> String {
    let dt = Local::now() + Duration::seconds(ttl);
    let ht = HumanTime::from(dt);

    format!("{}", ht)
}

fn fmt_record(r: &Record, human: bool) -> String {
    match *r.rdata() {
        RData::A(ip) => format!("IPv4:\t{}", ip),
        RData::AAAA(ip) => format!("IPv6:\t{}", ip),
        RData::CNAME(ref name) => {
            format!("CNAME:\t{}", Colour::Blue.paint(format!("{}", name)))
        }
        RData::MX(ref mx) => {
            format!(
                "MX:\t{} with preference {}",
                Colour::Yellow.paint(format!("{}", mx.exchange())),
                Colour::Yellow.paint(format!("{}", mx.preference()))
            )
        }
        RData::NS(ref name) => format!("NS:\t{}", Colour::Cyan.paint(format!("{}", name))),
        RData::SOA(ref soa) => {
            // TODO: time interval is in sec, use relative time
            format!(
                "SOA:\torigin NS {}, responsible party {}, serial {}, refresh {} sec, retry {} sec, expire {} sec, min {} sec",
                Colour::Green.paint(format!("{}", soa.mname())),
                Colour::Green.paint(format!("{}", soa.rname())),
                Colour::Green.paint(format!("{}", soa.serial())),
                Colour::Green.paint(format!("{}", soa.refresh())),
                Colour::Green.paint(format!("{}", soa.retry())),
                Colour::Green.paint(format!("{}", soa.expire())),
                Colour::Green.paint(format!("{}", soa.minimum()))
            )
        }
        RData::TXT(ref txt) => {
            format!("TXT:\t{}", Colour::Purple.paint(
                if human {
                    fmt_txt(txt.txt_data())
                } else {
                    txt.txt_data().join(" ")
                }
            ))
        }
        RData::PTR(ref ptr) => format!("PTR:\t{}", ptr.to_string()),
        ref x => {
            format!(
                " * unclassified answer: {}",
                Colour::Red.paint(format!("{:?}", x))
            )
        }
    }
}

fn fmt_txt(txts: &[String]) -> String {
    let fmts: Vec<_> = txts
        .iter()
        .map(|txt| {
            if let Ok(spf) = Spf::from_str(txt) {
                fmt_txt_spf(&spf)
            } else {
                format!("{}", txt)
            }
        })
        .collect();
    fmts.iter().join("\t* ")
}

fn fmt_txt_spf(spf: &Spf) -> String {
    let words: Vec<_> = spf.words
        .iter()
        .map(|w| {
            match w {
                &Word::Word(ref q, Mechanism::All) => format!("{:?} for all", q),
                &Word::Word(ref q, Mechanism::A) => format!("{:?} for A/AAAA record", q),
                &Word::Word(ref q, Mechanism::IPv4(range)) if range.contains("/") => format!("{:?} for IPv4 range {}", q, range),
                &Word::Word(ref q, Mechanism::IPv4(range)) => format!("{:?} for IPv4 {}", q, range),
                &Word::Word(ref q, Mechanism::IPv6(range)) if range.contains("/") => format!("{:?} for IPv6 range {}", q, range),
                &Word::Word(ref q, Mechanism::IPv6(range)) => format!("{:?} for IPv6 {}", q, range),
                &Word::Word(ref q, Mechanism::MX) => format!("{:?} for mail exchanges", q),
                &Word::Word(ref q, Mechanism::PTR) => format!("{:?} for reverse mapping", q),
                &Word::Word(ref q, Mechanism::Exists(domain)) => format!("{:?} for A/AAAA record according to {}", q, domain),
                &Word::Word(ref q, Mechanism::Include(domain)) => format!("{:?} for include from {}", q, domain),
                &Word::Modifier(Modifier::Redirect(query)) => format!("redirect to query {}", query),
                &Word::Modifier(Modifier::Exp(explanation)) => format!("explanation according to {}", explanation),
            }
        })
        .collect();
    format!("SPF version: {}\n\t* {}", spf.version, words.join("\n\t* "))
}

pub fn print_error<T: ChainedError>(w: &mut Write, err: &T) -> Result<()> {
    write!(w, "{} ", err).chain_err(|| ErrorKind::OutputError)?;
    for e in err.iter().skip(1) {
        write!(w, "because {}", e).chain_err(|| ErrorKind::OutputError)?;
    }
    writeln!(w).chain_err(|| ErrorKind::OutputError)?;

    // The backtrace is only available if run with `RUST_BACKTRACE=1`.
    if let Some(backtrace) = err.backtrace() {
        writeln!(w, "backtrace: {:?}", backtrace).chain_err(|| ErrorKind::OutputError)?;
    }

    Ok(())
}

error_chain! {
    errors {
        OutputError {
            description("Failed to write output")
            display("Failed to write output")
        }
    }
}
<commit_msg>Add relative time for SOA record<commit_after>use lookup;
use txt_records::{Spf, Word, Mechanism, Modifier};
use statistics::Statistics;

use ansi_term::Colour;
use chrono::{Local, Duration};
use chrono_humanize::HumanTime;
use error_chain::ChainedError;
use itertools::Itertools;
use std::cmp::Ordering;
use std::io::{self, Write};
use tabwriter::TabWriter;
use trust_dns::rr::{RData, Record};

pub trait OutputModule {
    fn output(&self, w: &mut Write) -> Result<()>;
    fn is_human(&self) -> bool;
}

pub struct DetailsOutput<'a> {
    responses: &'a [lookup::Result<lookup::Response>],
    human: bool,
}

impl<'a> DetailsOutput<'a> {
    pub fn new(responses: &'a [lookup::Result<lookup::Response>], human: bool) -> Self {
        DetailsOutput { responses, human }
    }
}

impl<'a> OutputModule for DetailsOutput<'a> {
    fn output(&self, w: &mut Write) -> Result<()> {
        for response in self.responses {
            match *response {
                Ok(ref r) => write_response(w, r, self.human).chain_err(|| ErrorKind::OutputError)?,
                Err(ref e) => print_error(w, e)?,
            }
        }

        Ok(())
    }

    fn is_human(&self) -> bool {
        self.human
    }
}

pub struct SummaryOutput<'a> {
    statistics: Statistics<'a>,
    human: bool,
}

impl<'a> SummaryOutput<'a> {
    pub fn new(responses: &'a [lookup::Result<lookup::Response>], human: bool) -> Self {
        let statistics = Statistics::from(responses);
        SummaryOutput { statistics, human }
    }
}

impl<'a> OutputModule for SummaryOutput<'a> {
    fn output(&self, w: &mut Write) -> Result<()> {
        writeln!(w, "Received {} (min {}, max {} records) answers from {} servers",
                 self.statistics.num_of_ok_samples,
                 self.statistics.min_num_of_records,
                 self.statistics.max_num_of_records,
                 self.statistics.num_of_samples,
        ).chain_err(|| ErrorKind::OutputError)?;
        let records: Vec<_> = self.statistics
            .record_counts
            .values()
            // TODO: Why do I need to specify a closure and not just a function?
            .sorted_by(|a, b| compare_records(a.0, b.0))
            .iter()
            .map(|rr| {
                let record = rr.0;
                let count = rr.1;
                format!("* {} ({})", fmt_record(record, self.human), count)
            })
            .collect();

        let mut tw = TabWriter::new(vec![]).padding(1);
        write!(&mut tw, "{}", records.join("\n")).chain_err(|| ErrorKind::OutputError)?;
        let out_str = String::from_utf8(tw.into_inner().unwrap()).unwrap();

        writeln!(w, "{}", out_str).chain_err(|| ErrorKind::OutputError)?;

        Ok(())
    }

    fn is_human(&self) -> bool {
        self.human
    }
}

fn write_response(f: &mut Write, r: &lookup::Response, human: bool) -> io::Result<()> {
    if r.answers.is_empty() {
        return writeln!(f, "DNS server {} has no records.", r.server);
    }
    let _ = write!(f, "DNS server {} responded with\n", r.server);
    let answers: Vec<String> = r.answers
        .iter()
        .sorted_by(|a, b| compare_records(a, b))
        .iter()
        .map(|answer|
            format!("* {} [expires {}]",
                    fmt_record(answer, human),
                    if human {
                        humanize_ttl(answer.ttl() as i64)
                    } else {
                        format!("in {} sec", answer.ttl())
                    }
            )
        )
        .collect();

    let mut tw = TabWriter::new(vec![]).padding(1);
    let _ = write!(&mut tw, "{}", answers.join("\n"));
    let out_str = String::from_utf8(tw.into_inner().unwrap()).unwrap();

    writeln!(f, "{}", out_str)
}

fn compare_records(a: &Record, b: &Record) -> Ordering {
    let a = record_type_to_ordinal(a);
    let b = record_type_to_ordinal(b);

    a.cmp(&b)
}

fn record_type_to_ordinal(r: &Record) -> u16 {
    match *r.rdata() {
        RData::SOA(_) => 1000,
        RData::NS(_) => 2000,
        RData::MX(ref mx) => 3000 + mx.preference(),
        RData::TXT(_) => 4000,
        RData::CNAME(_) => 5000,
        RData::A(_) => 6000,
        RData::AAAA(_) => 7000,
        RData::PTR(_) => 8000,
        _ => ::std::u16::MAX,
    }
}

fn humanize_ttl(ttl: i64) -> String {
    let dt = Local::now() + Duration::seconds(ttl);
    let ht = HumanTime::from(dt);

    format!("{}", ht)
}

fn fmt_record(r: &Record, human: bool) -> String {
    match *r.rdata() {
        RData::A(ip) => format!("IPv4:\t{}", ip),
        RData::AAAA(ip) => format!("IPv6:\t{}", ip),
        RData::CNAME(ref name) => {
            format!("CNAME:\t{}", Colour::Blue.paint(format!("{}", name)))
        }
        RData::MX(ref mx) => {
            format!(
                "MX:\t{} with preference {}",
                Colour::Yellow.paint(format!("{}", mx.exchange())),
                Colour::Yellow.paint(format!("{}", mx.preference()))
            )
        }
        RData::NS(ref name) => format!("NS:\t{}", Colour::Cyan.paint(format!("{}", name))),
        RData::SOA(ref soa) => {
            if human {
                format!(
                    "SOA:\torigin NS {}, responsible party {}, serial {}, refresh {}, retry {}, expire {}, min {}",
                    Colour::Green.paint(format!("{}", soa.mname())),
                    Colour::Green.paint(format!("{}", soa.rname())),
                    Colour::Green.paint(format!("{}", soa.serial())),
                    Colour::Green.paint(format!("{}", humanize_ttl(soa.refresh() as i64))),
                    Colour::Green.paint(format!("{}", humanize_ttl(soa.retry() as i64))),
                    Colour::Green.paint(format!("{}", humanize_ttl(soa.expire() as i64))),
                    Colour::Green.paint(format!("{}", humanize_ttl(soa.minimum() as i64)))
                )
            } else {
                format!(
                    "SOA:\torigin NS {}, responsible party {}, serial {}, refresh {} sec, retry {} sec, expire {} sec, min {} sec",
                    Colour::Green.paint(format!("{}", soa.mname())),
                    Colour::Green.paint(format!("{}", soa.rname())),
                    Colour::Green.paint(format!("{}", soa.serial())),
                    Colour::Green.paint(format!("{}", soa.refresh())),
                    Colour::Green.paint(format!("{}", soa.retry())),
                    Colour::Green.paint(format!("{}", soa.expire())),
                    Colour::Green.paint(format!("{}", soa.minimum()))
                )

            }
        }
        RData::TXT(ref txt) => {
            format!("TXT:\t{}", Colour::Purple.paint(
                if human {
                    fmt_txt(txt.txt_data())
                } else {
                    txt.txt_data().join(" ")
                }
            ))
        }
        RData::PTR(ref ptr) => format!("PTR:\t{}", ptr.to_string()),
        ref x => {
            format!(
                " * unclassified answer: {}",
                Colour::Red.paint(format!("{:?}", x))
            )
        }
    }
}

fn fmt_txt(txts: &[String]) -> String {
    let fmts: Vec<_> = txts
        .iter()
        .map(|txt| {
            if let Ok(spf) = Spf::from_str(txt) {
                fmt_txt_spf(&spf)
            } else {
                format!("{}", txt)
            }
        })
        .collect();
    fmts.iter().join("\t* ")
}

fn fmt_txt_spf(spf: &Spf) -> String {
    let words: Vec<_> = spf.words
        .iter()
        .map(|w| {
            match w {
                &Word::Word(ref q, Mechanism::All) => format!("{:?} for all", q),
                &Word::Word(ref q, Mechanism::A) => format!("{:?} for A/AAAA record", q),
                &Word::Word(ref q, Mechanism::IPv4(range)) if range.contains("/") => format!("{:?} for IPv4 range {}", q, range),
                &Word::Word(ref q, Mechanism::IPv4(range)) => format!("{:?} for IPv4 {}", q, range),
                &Word::Word(ref q, Mechanism::IPv6(range)) if range.contains("/") => format!("{:?} for IPv6 range {}", q, range),
                &Word::Word(ref q, Mechanism::IPv6(range)) => format!("{:?} for IPv6 {}", q, range),
                &Word::Word(ref q, Mechanism::MX) => format!("{:?} for mail exchanges", q),
                &Word::Word(ref q, Mechanism::PTR) => format!("{:?} for reverse mapping", q),
                &Word::Word(ref q, Mechanism::Exists(domain)) => format!("{:?} for A/AAAA record according to {}", q, domain),
                &Word::Word(ref q, Mechanism::Include(domain)) => format!("{:?} for include from {}", q, domain),
                &Word::Modifier(Modifier::Redirect(query)) => format!("redirect to query {}", query),
                &Word::Modifier(Modifier::Exp(explanation)) => format!("explanation according to {}", explanation),
            }
        })
        .collect();
    format!("SPF version: {}\n\t* {}", spf.version, words.join("\n\t* "))
}

pub fn print_error<T: ChainedError>(w: &mut Write, err: &T) -> Result<()> {
    write!(w, "{} ", err).chain_err(|| ErrorKind::OutputError)?;
    for e in err.iter().skip(1) {
        write!(w, "because {}", e).chain_err(|| ErrorKind::OutputError)?;
    }
    writeln!(w).chain_err(|| ErrorKind::OutputError)?;

    // The backtrace is only available if run with `RUST_BACKTRACE=1`.
    if let Some(backtrace) = err.backtrace() {
        writeln!(w, "backtrace: {:?}", backtrace).chain_err(|| ErrorKind::OutputError)?;
    }

    Ok(())
}

error_chain! {
    errors {
        OutputError {
            description("Failed to write output")
            display("Failed to write output")
        }
    }
}
<|endoftext|>


<commit_before>use memory::Memory;
use std;

/// Like `/dev/null`.
///
/// The `NullMemory` is always 2^32 bytes large (osci’s memory size),
/// reads `0` everywhere and discards all writes.
///
/// # Examples
/// ```
/// use osciemu::memory::Memory;
/// use osciemu::memory::NullMemory;
///
/// let mut m = NullMemory::new();
/// assert_eq!(m.get(0), 0);
/// m.set(0, 1);
/// assert_eq!(m.get(0), 0);
/// ```
pub struct NullMemory;

impl NullMemory {
    pub fn new() -> NullMemory {
        NullMemory
    }
}

impl Memory for NullMemory {
    #[inline]
    fn get(&self, _: usize) -> u32 {
        0
    }

    #[inline]
    fn set(&mut self, _: usize, _: u32) {}

    #[inline]
    fn size(&self) -> usize {
        std::u32::MAX as usize
    }
}

#[cfg(test)]
mod test {
    use memory::Memory;

    #[test]
    fn nullmemory_read() {
        let m = super::NullMemory::new();
        assert_eq!(m.get(0), 0);
        assert_eq!(m.get(123), 0);
        assert_eq!(m.get(u32::max_value() as usize), 0);
    }

    #[test]
    fn nullmemory_write() {
        let mut m = super::NullMemory::new();
        assert_eq!(m.get(0), 0);
        m.set(0, 4);
        assert_eq!(m.get(0), 0);
    }
}
<commit_msg>style(nullmemory): Remove type name from test functions<commit_after>use memory::Memory;
use std;

/// Like `/dev/null`.
///
/// The `NullMemory` is always 2^32 bytes large (osci’s memory size),
/// reads `0` everywhere and discards all writes.
///
/// # Examples
/// ```
/// use osciemu::memory::Memory;
/// use osciemu::memory::NullMemory;
///
/// let mut m = NullMemory::new();
/// assert_eq!(m.get(0), 0);
/// m.set(0, 1);
/// assert_eq!(m.get(0), 0);
/// ```
pub struct NullMemory;

impl NullMemory {
    pub fn new() -> NullMemory {
        NullMemory
    }
}

impl Memory for NullMemory {
    #[inline]
    fn get(&self, _: usize) -> u32 {
        0
    }

    #[inline]
    fn set(&mut self, _: usize, _: u32) {}

    #[inline]
    fn size(&self) -> usize {
        std::u32::MAX as usize
    }
}

#[cfg(test)]
mod test {
    use memory::Memory;

    #[test]
    fn read() {
        let m = super::NullMemory::new();
        assert_eq!(m.get(0), 0);
        assert_eq!(m.get(123), 0);
        assert_eq!(m.get(u32::max_value() as usize), 0);
    }

    #[test]
    fn write() {
        let mut m = super::NullMemory::new();
        assert_eq!(m.get(0), 0);
        m.set(0, 4);
        assert_eq!(m.get(0), 0);
    }
}
<|endoftext|>


<commit_before>//! WlOutput advertising capabilities
//!
//! This module provides additional implementations for [`crate::output::Output`]
//! to handle advertising of your compositor's output and their capabilities to your client,
//! as well as mapping your clients output request to your physical outputs.
//!
//! # How to use it
//!
//! After you have instantiated an [`Output`] you need to use [`Output::create_global`]
//! to advertise a new output global to clients.
//! The resulting `GlobalId` can later be destroyed again to stop advertising it
//! without destroying it's state. E.g. in case the matching physical output got disabled at runtime.
//!
//! If you change the properties of your output (if the current resolution mode changes for example),
//! it'll automatically forward any changes to the clients.
//!
//! Additional protocols may piggy-back on this type.
//! E.g. to also advertise an xdg-output for every wl-output you can use
//! [`OutputManagerState::new_with_xdg_output`].
//!
//! ```
//! # extern crate wayland_server;
//! # extern crate smithay;
//! use smithay::delegate_output;
//! use smithay::output::{Output, PhysicalProperties, Scale, Mode, Subpixel};
//! use smithay::utils::Transform;
//!
//! # struct State;
//! # let mut display = wayland_server::Display::<State>::new().unwrap();
//! # let display_handle = display.handle();
//! // Create the Output with given name and physical properties.
//! let output = Output::new(
//!     "output-0".into(), // the name of this output,
//!     PhysicalProperties {
//!         size: (200, 150).into(),        // dimensions (width, height) in mm
//!         subpixel: Subpixel::HorizontalRgb,  // subpixel information
//!         make: "Screens Inc".into(),     // make of the monitor
//!         model: "Monitor Ultra".into(),  // model of the monitor
//!     },
//!     None // insert a logger here
//! );
//! // create a global, if you want to advertise it to clients
//! let _global = output.create_global::<State>(
//!     &display_handle,      // the display
//! ); // you can drop the global, if you never intend to destroy it.
//! // Now you can configure it
//! output.change_current_state(
//!     Some(Mode { size: (1920, 1080).into(), refresh: 60000 }), // the resolution mode,
//!     Some(Transform::Normal), // global screen transformation
//!     Some(Scale::Integer(1)), // global screen scaling factor
//!     Some((0,0).into()) // output position
//! );
//! // set the preferred mode
//! output.set_preferred(Mode { size: (1920, 1080).into(), refresh: 60000 });
//! // add other supported modes
//! output.add_mode(Mode { size: (800, 600).into(), refresh: 60000 });
//! output.add_mode(Mode { size: (1024, 768).into(), refresh: 60000 });
//!
//! delegate_output!(State);
//! ```

mod handlers;
pub(crate) mod xdg;
use crate::output::{Inner, Mode, Output, OutputData, Scale, Subpixel};

use wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1;
use wayland_server::{
    backend::GlobalId,
    protocol::{
        wl_output::{Mode as WMode, Subpixel as WlSubpixel, Transform, WlOutput},
        wl_surface,
    },
    Client, DisplayHandle, GlobalDispatch, Resource,
};

use crate::utils::{Logical, Point};

pub use self::handlers::XdgOutputUserData;

/// State of Smithay output manager
#[derive(Debug, Default)]
pub struct OutputManagerState {
    xdg_output_manager: Option<GlobalId>,
}

/// Internal data of a wl_output global
#[derive(Debug)]
pub struct WlOutputData {
    inner: OutputData,
}

impl OutputManagerState {
    /// Create new output manager
    pub fn new() -> Self {
        Self {
            xdg_output_manager: None,
        }
    }

    /// Create new output manager with xdg output support
    pub fn new_with_xdg_output<D>(display: &DisplayHandle) -> Self
    where
        D: GlobalDispatch<WlOutput, WlOutputData>,
        D: GlobalDispatch<ZxdgOutputManagerV1, ()>,
        D: 'static,
    {
        let xdg_output_manager = display.create_global::<D, ZxdgOutputManagerV1, _>(3, ());

        Self {
            xdg_output_manager: Some(xdg_output_manager),
        }
    }

    /// Get global id of xdg output manager
    pub fn xdg_output_manager_global(&self) -> Option<GlobalId> {
        self.xdg_output_manager.clone()
    }
}

/// User data for WlOutput
#[derive(Debug, Clone)]
pub struct OutputUserData {
    pub(crate) global_data: OutputData,
}

impl Inner {
    fn send_geometry_to(&self, output: &WlOutput) {
        output.geometry(
            self.location.x,
            self.location.y,
            self.physical.size.w,
            self.physical.size.h,
            self.physical.subpixel.into(),
            self.physical.make.clone(),
            self.physical.model.clone(),
            self.transform.into(),
        );
    }
}

impl From<Subpixel> for WlSubpixel {
    fn from(s: Subpixel) -> Self {
        match s {
            Subpixel::HorizontalBgr => WlSubpixel::HorizontalBgr,
            Subpixel::HorizontalRgb => WlSubpixel::HorizontalRgb,
            Subpixel::None => WlSubpixel::None,
            Subpixel::Unknown => WlSubpixel::Unknown,
            Subpixel::VerticalBgr => WlSubpixel::VerticalBgr,
            Subpixel::VerticalRgb => WlSubpixel::VerticalRgb,
        }
    }
}

impl Output {
    /// Create a new output global.
    ///
    /// The global is directly registered into the event loop, and this function
    /// returns the global handle in case you wish to remove this global in the future.
    ///
    /// Calling this function multiple times without destroying the global in between,
    /// will result in multiple globals, meaning the output will be advertised to clients
    /// multiple times.
    pub fn create_global<D>(&self, display: &DisplayHandle) -> GlobalId
    where
        D: GlobalDispatch<WlOutput, WlOutputData>,
        D: 'static,
    {
        display.create_global::<D, WlOutput, _>(
            4,
            WlOutputData {
                inner: self.inner.clone(),
            },
        )
    }

    /// Attempt to retrieve a [`Output`] from an existing resource
    pub fn from_resource(output: &WlOutput) -> Option<Output> {
        output.data::<OutputUserData>().map(|ud| Output {
            inner: ud.global_data.clone(),
        })
    }

    pub(crate) fn wl_change_current_state(
        &self,
        new_mode: Option<Mode>,
        new_transform: Option<Transform>,
        new_scale: Option<Scale>,
        new_location: Option<Point<i32, Logical>>,
    ) {
        let inner = self.inner.0.lock().unwrap();
        // XdgOutput has to be updated before WlOutput
        // Because WlOutput::done() has to allways be called last
        if let Some(xdg_output) = inner.xdg_output.as_ref() {
            xdg_output.change_current_state(new_mode, new_scale, new_location);
        }

        let mut flags = WMode::Current;
        if inner.preferred_mode == new_mode {
            flags |= WMode::Preferred;
        }

        for output in &inner.instances {
            if let Some(mode) = new_mode {
                output.mode(flags, mode.size.w, mode.size.h, mode.refresh);
            }
            if new_transform.is_some() || new_location.is_some() {
                inner.send_geometry_to(output);
            }
            if let Some(scale) = new_scale {
                if output.version() >= 2 {
                    output.scale(scale.integer_scale());
                }
            }
            if output.version() >= 2 {
                output.done();
            }
        }
    }

    /// Check is given [`wl_output`](WlOutput) instance is managed by this [`Output`].
    pub fn owns(&self, output: &WlOutput) -> bool {
        self.inner
            .0
            .lock()
            .unwrap()
            .instances
            .iter()
            .any(|o| o.id() == output.id())
    }

    /// This function allows to run a [FnMut] on every
    /// [WlOutput] matching the same [Client] as provided
    pub fn with_client_outputs<F>(&self, dh: &DisplayHandle, client: &Client, mut f: F)
    where
        F: FnMut(&DisplayHandle, &WlOutput),
    {
        let list: Vec<_> = self
            .inner
            .0
            .lock()
            .unwrap()
            .instances
            .iter()
            .filter(|output| {
                dh.get_client(output.id())
                    .map(|output_client| &output_client == client)
                    .unwrap_or(false)
            })
            .cloned()
            .collect();

        for o in list {
            f(dh, &o);
        }
    }

    /// Sends `wl_surface.enter` for the provided surface
    /// with the matching client output
    pub fn enter(&self, dh: &DisplayHandle, surface: &wl_surface::WlSurface) {
        if let Ok(client) = dh.get_client(surface.id()) {
            self.with_client_outputs(dh, &client, |_dh, output| surface.enter(output))
        }
    }

    /// Sends `wl_surface.leave` for the provided surface
    /// with the matching client output
    pub fn leave(&self, dh: &DisplayHandle, surface: &wl_surface::WlSurface) {
        if let Ok(client) = dh.get_client(surface.id()) {
            self.with_client_outputs(dh, &client, |_dh, output| surface.leave(output))
        }
    }
}

#[allow(missing_docs)] // TODO
#[macro_export]
macro_rules! delegate_output {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_server::protocol::wl_output::WlOutput: $crate::wayland::output::WlOutputData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1: ()
        ] => $crate::wayland::output::OutputManagerState);

        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_server::protocol::wl_output::WlOutput: $crate::wayland::output::OutputUserData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_v1::ZxdgOutputV1: $crate::wayland::output::XdgOutputUserData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1: ()
        ] => $crate::wayland::output::OutputManagerState);
    };
}
<commit_msg>output: Remove DIsplayHandle parameter<commit_after>//! WlOutput advertising capabilities
//!
//! This module provides additional implementations for [`crate::output::Output`]
//! to handle advertising of your compositor's output and their capabilities to your client,
//! as well as mapping your clients output request to your physical outputs.
//!
//! # How to use it
//!
//! After you have instantiated an [`Output`] you need to use [`Output::create_global`]
//! to advertise a new output global to clients.
//! The resulting `GlobalId` can later be destroyed again to stop advertising it
//! without destroying it's state. E.g. in case the matching physical output got disabled at runtime.
//!
//! If you change the properties of your output (if the current resolution mode changes for example),
//! it'll automatically forward any changes to the clients.
//!
//! Additional protocols may piggy-back on this type.
//! E.g. to also advertise an xdg-output for every wl-output you can use
//! [`OutputManagerState::new_with_xdg_output`].
//!
//! ```
//! # extern crate wayland_server;
//! # extern crate smithay;
//! use smithay::delegate_output;
//! use smithay::output::{Output, PhysicalProperties, Scale, Mode, Subpixel};
//! use smithay::utils::Transform;
//!
//! # struct State;
//! # let mut display = wayland_server::Display::<State>::new().unwrap();
//! # let display_handle = display.handle();
//! // Create the Output with given name and physical properties.
//! let output = Output::new(
//!     "output-0".into(), // the name of this output,
//!     PhysicalProperties {
//!         size: (200, 150).into(),        // dimensions (width, height) in mm
//!         subpixel: Subpixel::HorizontalRgb,  // subpixel information
//!         make: "Screens Inc".into(),     // make of the monitor
//!         model: "Monitor Ultra".into(),  // model of the monitor
//!     },
//!     None // insert a logger here
//! );
//! // create a global, if you want to advertise it to clients
//! let _global = output.create_global::<State>(
//!     &display_handle,      // the display
//! ); // you can drop the global, if you never intend to destroy it.
//! // Now you can configure it
//! output.change_current_state(
//!     Some(Mode { size: (1920, 1080).into(), refresh: 60000 }), // the resolution mode,
//!     Some(Transform::Normal), // global screen transformation
//!     Some(Scale::Integer(1)), // global screen scaling factor
//!     Some((0,0).into()) // output position
//! );
//! // set the preferred mode
//! output.set_preferred(Mode { size: (1920, 1080).into(), refresh: 60000 });
//! // add other supported modes
//! output.add_mode(Mode { size: (800, 600).into(), refresh: 60000 });
//! output.add_mode(Mode { size: (1024, 768).into(), refresh: 60000 });
//!
//! delegate_output!(State);
//! ```

mod handlers;
pub(crate) mod xdg;
use crate::output::{Inner, Mode, Output, OutputData, Scale, Subpixel};

use wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1;
use wayland_server::{
    backend::GlobalId,
    protocol::{
        wl_output::{Mode as WMode, Subpixel as WlSubpixel, Transform, WlOutput},
        wl_surface,
    },
    Client, DisplayHandle, GlobalDispatch, Resource,
};

use crate::utils::{Logical, Point};

pub use self::handlers::XdgOutputUserData;

/// State of Smithay output manager
#[derive(Debug, Default)]
pub struct OutputManagerState {
    xdg_output_manager: Option<GlobalId>,
}

/// Internal data of a wl_output global
#[derive(Debug)]
pub struct WlOutputData {
    inner: OutputData,
}

impl OutputManagerState {
    /// Create new output manager
    pub fn new() -> Self {
        Self {
            xdg_output_manager: None,
        }
    }

    /// Create new output manager with xdg output support
    pub fn new_with_xdg_output<D>(display: &DisplayHandle) -> Self
    where
        D: GlobalDispatch<WlOutput, WlOutputData>,
        D: GlobalDispatch<ZxdgOutputManagerV1, ()>,
        D: 'static,
    {
        let xdg_output_manager = display.create_global::<D, ZxdgOutputManagerV1, _>(3, ());

        Self {
            xdg_output_manager: Some(xdg_output_manager),
        }
    }

    /// Get global id of xdg output manager
    pub fn xdg_output_manager_global(&self) -> Option<GlobalId> {
        self.xdg_output_manager.clone()
    }
}

/// User data for WlOutput
#[derive(Debug, Clone)]
pub struct OutputUserData {
    pub(crate) global_data: OutputData,
}

impl Inner {
    fn send_geometry_to(&self, output: &WlOutput) {
        output.geometry(
            self.location.x,
            self.location.y,
            self.physical.size.w,
            self.physical.size.h,
            self.physical.subpixel.into(),
            self.physical.make.clone(),
            self.physical.model.clone(),
            self.transform.into(),
        );
    }
}

impl From<Subpixel> for WlSubpixel {
    fn from(s: Subpixel) -> Self {
        match s {
            Subpixel::HorizontalBgr => WlSubpixel::HorizontalBgr,
            Subpixel::HorizontalRgb => WlSubpixel::HorizontalRgb,
            Subpixel::None => WlSubpixel::None,
            Subpixel::Unknown => WlSubpixel::Unknown,
            Subpixel::VerticalBgr => WlSubpixel::VerticalBgr,
            Subpixel::VerticalRgb => WlSubpixel::VerticalRgb,
        }
    }
}

impl Output {
    /// Create a new output global.
    ///
    /// The global is directly registered into the event loop, and this function
    /// returns the global handle in case you wish to remove this global in the future.
    ///
    /// Calling this function multiple times without destroying the global in between,
    /// will result in multiple globals, meaning the output will be advertised to clients
    /// multiple times.
    pub fn create_global<D>(&self, display: &DisplayHandle) -> GlobalId
    where
        D: GlobalDispatch<WlOutput, WlOutputData>,
        D: 'static,
    {
        self.data.inner.0.lock().unwrap().dh = Some(display.clone());
        display.create_global::<D, WlOutput, _>(
            4,
            WlOutputData {
                inner: self.inner.clone(),
            },
        )
    }

    /// Attempt to retrieve a [`Output`] from an existing resource
    pub fn from_resource(output: &WlOutput) -> Option<Output> {
        output.data::<OutputUserData>().map(|ud| Output {
            inner: ud.global_data.clone(),
        })
    }

    pub(crate) fn wl_change_current_state(
        &self,
        new_mode: Option<Mode>,
        new_transform: Option<Transform>,
        new_scale: Option<Scale>,
        new_location: Option<Point<i32, Logical>>,
    ) {
        let inner = self.inner.0.lock().unwrap();
        // XdgOutput has to be updated before WlOutput
        // Because WlOutput::done() has to allways be called last
        if let Some(xdg_output) = inner.xdg_output.as_ref() {
            xdg_output.change_current_state(new_mode, new_scale, new_location);
        }

        let mut flags = WMode::Current;
        if inner.preferred_mode == new_mode {
            flags |= WMode::Preferred;
        }

        for output in &inner.instances {
            if let Some(mode) = new_mode {
                output.mode(flags, mode.size.w, mode.size.h, mode.refresh);
            }
            if new_transform.is_some() || new_location.is_some() {
                inner.send_geometry_to(output);
            }
            if let Some(scale) = new_scale {
                if output.version() >= 2 {
                    output.scale(scale.integer_scale());
                }
            }
            if output.version() >= 2 {
                output.done();
            }
        }
    }

    /// Check is given [`wl_output`](WlOutput) instance is managed by this [`Output`].
    pub fn owns(&self, output: &WlOutput) -> bool {
        self.inner
            .0
            .lock()
            .unwrap()
            .instances
            .iter()
            .any(|o| o.id() == output.id())
    }

    /// This function allows to run a [FnMut] on every
    /// [WlOutput] matching the same [Client] as provided
    pub fn with_client_outputs<F>(&self, client: &Client, mut f: F)
    where
        F: FnMut(&WlOutput),
    {
        let list: Vec<WlOutput> = {
            let data = self.inner.0.lock().unwrap();
            data.instances
                .iter()
                .filter(|output| {
                    data.dh
                        .as_ref()
                        .map(|dh| {
                            dh.get_client(output.id())
                                .map(|output_client| &output_client == client)
                                .unwrap_or(false)
                        })
                        .unwrap_or(false)
                })
                .cloned()
                .collect()
        };

        for o in list {
            f(&o);
        }
    }

    /// Sends `wl_surface.enter` for the provided surface
    /// with the matching client output
    pub fn enter(&self, surface: &wl_surface::WlSurface) {
        let client = self
            .data
            .inner
            .0
            .lock()
            .unwrap()
            .dh
            .as_ref()
            .and_then(|dh| dh.get_client(surface.id()).ok());
        if let Some(client) = client {
            self.with_client_outputs(&client, |output| surface.enter(output))
        }
    }

    /// Sends `wl_surface.leave` for the provided surface
    /// with the matching client output
    pub fn leave(&self, surface: &wl_surface::WlSurface) {
        let client = self
            .data
            .inner
            .0
            .lock()
            .unwrap()
            .dh
            .as_ref()
            .and_then(|dh| dh.get_client(surface.id()).ok());
        if let Some(client) = client {
            self.with_client_outputs(&client, |output| surface.leave(output))
        }
    }
}

#[allow(missing_docs)] // TODO
#[macro_export]
macro_rules! delegate_output {
    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_server::protocol::wl_output::WlOutput: $crate::wayland::output::WlOutputData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1: ()
        ] => $crate::wayland::output::OutputManagerState);

        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_server::protocol::wl_output::WlOutput: $crate::wayland::output::OutputUserData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_v1::ZxdgOutputV1: $crate::wayland::output::XdgOutputUserData
        ] => $crate::wayland::output::OutputManagerState);
        $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
            $crate::reexports::wayland_protocols::xdg::xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1: ()
        ] => $crate::wayland::output::OutputManagerState);
    };
}
<|endoftext|>


<commit_before>//! The parser functionality

use crate::bindings::*;
use crate::c_helpers::*;
use crate::tree::*;

use std::convert::AsRef;
use std::io;
use std::ffi::{CStr, CString};
use std::fmt;
use std::fs;
use std::ptr;
use std::slice;
use std::str;

enum XmlParserOption {
  Recover = 1, // Relaxed parsing
  // XML_PARSE_NODEFDTD = 4, // do not default a doctype if not found
  Noerror = 32, // suppress error reports
  Nowarning = 64, // suppress warning reports
                // XML_PARSE_PEDANTIC = 128, // pedantic error reporting
                // XML_PARSE_NOBLANKS = 256, // remove blank nodes
                // XML_PARSE_NONET = 2048, // Forbid network access
                // XML_PARSE_NOIMPLIED = 8192, // Do not add implied Xml/body... elements
                // XML_PARSE_COMPACT = 65536, // compact small text nodes
                // XML_PARSE_IGNORE_ENC = 2097152, // ignore internal document encoding hint
}

enum HtmlParserOption {
  Recover = 1, // Relaxed parsing
  // HTML_PARSE_NODEFDTD = 4, // do not default a doctype if not found
  Noerror = 32, // suppress error reports
  Nowarning = 64, // suppress warning reports
                // HTML_PARSE_PEDANTIC = 128, // pedantic error reporting
                // HTML_PARSE_NOBLANKS = 256, // remove blank nodes
                // HTML_PARSE_NONET = 2048, // Forbid network access
                // HTML_PARSE_NOIMPLIED = 8192, // Do not add implied html/body... elements
                // HTML_PARSE_COMPACT = 65536, // compact small text nodes
                // HTML_PARSE_IGNORE_ENC = 2097152, // ignore internal document encoding hint
}

///Parser Errors
pub enum XmlParseError {
  ///Parsing returned a null pointer as document pointer
  GotNullPointer,
  ///Could not open file error.
  FileOpenError,
  ///Document too large for libxml2.
  DocumentTooLarge,
}

impl fmt::Debug for XmlParseError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match *self {
      XmlParseError::GotNullPointer => write!(f, "Got a Null pointer"),
      XmlParseError::FileOpenError => write!(f, "Unable to open path to file."),
      XmlParseError::DocumentTooLarge => write!(f, "Document too large for i32."),
    }
  }
}

/// Default encoding when not provided.
const DEFAULT_ENCODING: *const ::std::os::raw::c_char = ptr::null();

/// Default URL when not provided.
const DEFAULT_URL: *const ::std::os::raw::c_char = ptr::null();

/// Open file function.
fn xml_open(filename: &str) -> io::Result<*mut ::std::os::raw::c_void> {
  let ptr = Box::into_raw(Box::new(fs::File::open(filename)?));
  Ok(ptr as *mut ::std::os::raw::c_void)
}

/// Read callback for an FS file.
unsafe extern "C" fn xml_read(
    context: *mut ::std::os::raw::c_void,
    buffer: *mut ::std::os::raw::c_char,
    len: ::std::os::raw::c_int,
) -> ::std::os::raw::c_int
{
  // Len is always positive, typically 40-4000 bytes.
  let file = context as *mut fs::File;
  let buf = slice::from_raw_parts_mut(buffer as *mut u8, len as usize);
  match io::Read::read(&mut *file, buf) {
    Ok(v) => v as ::std::os::raw::c_int,
    Err(_) => -1,
  }
}

type XmlReadCallback = unsafe extern "C" fn(
  *mut ::std::ffi::c_void,
  *mut ::std::os::raw::c_char,
  ::std::os::raw::c_int
) -> ::std::os::raw::c_int;

/// Close callback for an FS file.
unsafe extern "C" fn xml_close(
    context: *mut ::std::os::raw::c_void,
) -> ::std::os::raw::c_int
{
  // Take rust ownership of the context and then drop it.
  let file = context as *mut fs::File;
  let _ = Box::from_raw(file);
  0
}

type XmlCloseCallback = unsafe extern "C" fn(
  *mut ::std::ffi::c_void,
) -> ::std::os::raw::c_int;

///Convert usize to i32 safely.
fn usize_to_i32(value: usize) -> Result<i32, XmlParseError> {
  if cfg!(target_pointer_width = "16") {
    // Cannot safely use our value comparison, but the conversion if always safe.
      Ok(value as i32)
  } else {
    if value < i32::max_value() as usize {
      // If the value can be safely represented as a 32-bit signed integer.
      Ok(value as i32)
    } else {
      // Document too large, cannot parse using libxml2.
      Err(XmlParseError::DocumentTooLarge)
    }
  }
}

#[derive(PartialEq)]
/// Enum for the parse formats supported by libxml2
pub enum ParseFormat {
  /// Strict parsing for XML
  XML,
  /// Relaxed parsing for HTML
  HTML,
}
/// Parsing API wrapper for libxml2
pub struct Parser {
  /// The `ParseFormat` for this parser
  pub format: ParseFormat,
}
impl Default for Parser {
  /// Create a parser for XML documents
  fn default() -> Self {
    Parser {
      format: ParseFormat::XML,
    }
  }
}
impl Parser {
  /// Create a parser for HTML documents
  pub fn default_html() -> Self {
    Parser {
      format: ParseFormat::HTML,
    }
  }

  /// Parses the XML/HTML file `filename` to generate a new `Document`
  pub fn parse_file(&self, filename: &str) -> Result<Document, XmlParseError> {
    self.parse_file_with_encoding(filename, None)
  }

  /// Parses the XML/HTML file `filename` with a manually-specified encoding
  /// to generate a new `Document`
  pub fn parse_file_with_encoding(&self, filename: &str, encoding: Option<&str>)
    -> Result<Document, XmlParseError>
  {
    // Create extern C callbacks for to read and close a Rust file through
    // a void pointer.
    let ioread: Option<XmlReadCallback> = Some(xml_read);
    let ioclose: Option<XmlCloseCallback> = Some(xml_close);
    let ioctx = match xml_open(filename) {
      Ok(v) => v,
      Err(_) => return Err(XmlParseError::FileOpenError),
    };

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.
      map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;

    unsafe {
      xmlKeepBlanksDefault(1);
    }
    match self.format {
      ParseFormat::XML => {
        let options: i32 = XmlParserOption::Recover as i32
          + XmlParserOption::Noerror as i32
          + XmlParserOption::Nowarning as i32;
        unsafe {
          let doc_ptr = xmlReadIO(ioread, ioclose, ioctx, url_ptr, encoding_ptr, options);
          if doc_ptr.is_null() {
            Err(XmlParseError::GotNullPointer)
          } else {
            Ok(Document::new_ptr(doc_ptr))
          }
        }
      }
      ParseFormat::HTML => {
        // TODO: Allow user-specified options later on
        let options: i32 = HtmlParserOption::Recover as i32
          + HtmlParserOption::Noerror as i32
          + HtmlParserOption::Nowarning as i32;
        unsafe {
          let doc_ptr = htmlReadIO(ioread, ioclose, ioctx, url_ptr, encoding_ptr, options);
          if doc_ptr.is_null() {
            Err(XmlParseError::GotNullPointer)
          } else {
            Ok(Document::new_ptr(doc_ptr))
          }
        }
      }
    }
  }

  ///Parses the XML/HTML bytes `input` to generate a new `Document`
  pub fn parse_string<Bytes: AsRef<[u8]>>(&self, input: Bytes) -> Result<Document, XmlParseError> {
    self.parse_string_with_encoding(input, None)
  }

  ///Parses the XML/HTML bytes `input` with a manually-specified
  ///encoding to generate a new `Document`
  pub fn parse_string_with_encoding<Bytes: AsRef<[u8]>>(&self, input: Bytes, encoding: Option<&str>)
    -> Result<Document, XmlParseError>
  {
    // Process input bytes.
    let input_bytes = input.as_ref();
    let input_ptr = input_bytes.as_ptr() as *const ::std::os::raw::c_char;
    let input_len = usize_to_i32(input_bytes.len())?;

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.
      map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;
    match self.format {
      ParseFormat::XML => unsafe {
        let options: i32 = XmlParserOption::Recover as i32
          + XmlParserOption::Noerror as i32
          + XmlParserOption::Nowarning as i32;
        let docptr = xmlReadMemory(
          input_ptr,
          input_len,
          url_ptr,
          encoding_ptr,
          options,
        );
        if docptr.is_null() {
          Err(XmlParseError::GotNullPointer)
        } else {
          Ok(Document::new_ptr(docptr))
        }
      },
      ParseFormat::HTML => unsafe {
        let options: i32 = HtmlParserOption::Recover as i32
          + HtmlParserOption::Noerror as i32
          + HtmlParserOption::Nowarning as i32;
        let docptr = htmlReadMemory(
          input_ptr,
          input_len,
          url_ptr,
          encoding_ptr,
          options,
        );
        if docptr.is_null() {
          Err(XmlParseError::GotNullPointer)
        } else {
          Ok(Document::new_ptr(docptr))
        }
      },
    }
  }

  /// Checks a string for well-formedness.
  pub fn is_well_formed_html<Bytes: AsRef<[u8]>>(&self, input: Bytes) -> bool {
    self.is_well_formed_html_with_encoding(input, None)
  }

  /// Checks a string for well-formedness with manually-specified encoding.
  /// IMPORTANT: This function is currently implemented in a HACKY way, to ignore invalid errors for HTML5 elements (such as <math>)
  ///            this means you should NEVER USE IT WHILE THREADING, it is CERTAIN TO BREAK
  ///
  /// Help is welcome in implementing it correctly.
  pub fn is_well_formed_html_with_encoding<Bytes: AsRef<[u8]>>(&self, input: Bytes, encoding: Option<&str>)
    -> bool
  {
    // Process input string.
    let input_bytes = input.as_ref();
    if input_bytes.is_empty() {
      return false;
    }
    let input_ptr = input_bytes.as_ptr() as *const ::std::os::raw::c_char;
    let input_len = match usize_to_i32(input_bytes.len()) {
      Ok(v) => v,
      Err(_) => return false,
    };

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.
      map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;
    // disable generic error lines from libxml2
    match self.format {
      ParseFormat::XML => false, // TODO: Add support for XML at some point
      ParseFormat::HTML => unsafe {
        let ctxt = htmlNewParserCtxt();
        setWellFormednessHandler(ctxt);
        let docptr = htmlCtxtReadMemory (
          ctxt,
          input_ptr,
          input_len,
          url_ptr,
          encoding_ptr,
          10_596,
        ); // htmlParserOption = 4+32+64+256+2048+8192
        let well_formed_final = if htmlWellFormed(ctxt) {
          // Basic well-formedness passes, let's check if we have an <html> element as root too
          if !docptr.is_null() {
            let node_ptr = xmlDocGetRootElement(docptr);
            let name_ptr = xmlNodeGetName(node_ptr);
            if name_ptr.is_null() {
              false
            }
            //empty string
            else {
              let c_root_name = CStr::from_ptr(name_ptr);
              let root_name = str::from_utf8(c_root_name.to_bytes()).unwrap().to_owned();
              root_name == "html"
            }
          } else {
            false
          }
        } else {
          false
        };

        if !ctxt.is_null() {
          htmlFreeParserCtxt(ctxt);
        }
        if !docptr.is_null() {
          xmlFreeDoc(docptr);
        }
        well_formed_final
      },
    }
  }
}
<commit_msg>minor cleanup of new Parser code<commit_after>//! The parser functionality

use crate::bindings::*;
use crate::c_helpers::*;
use crate::tree::*;

use std::convert::AsRef;
use std::ffi::c_void;
use std::ffi::{CStr, CString};
use std::fmt;
use std::fs;
use std::io;
use std::os::raw::{c_char, c_int};
use std::ptr;
use std::slice;
use std::str;

enum XmlParserOption {
  Recover = 1, // Relaxed parsing
  // XML_PARSE_NODEFDTD = 4, // do not default a doctype if not found
  Noerror = 32, // suppress error reports
  Nowarning = 64, // suppress warning reports
                // XML_PARSE_PEDANTIC = 128, // pedantic error reporting
                // XML_PARSE_NOBLANKS = 256, // remove blank nodes
                // XML_PARSE_NONET = 2048, // Forbid network access
                // XML_PARSE_NOIMPLIED = 8192, // Do not add implied Xml/body... elements
                // XML_PARSE_COMPACT = 65536, // compact small text nodes
                // XML_PARSE_IGNORE_ENC = 2097152, // ignore internal document encoding hint
}

enum HtmlParserOption {
  Recover = 1, // Relaxed parsing
  // HTML_PARSE_NODEFDTD = 4, // do not default a doctype if not found
  Noerror = 32, // suppress error reports
  Nowarning = 64, // suppress warning reports
                // HTML_PARSE_PEDANTIC = 128, // pedantic error reporting
                // HTML_PARSE_NOBLANKS = 256, // remove blank nodes
                // HTML_PARSE_NONET = 2048, // Forbid network access
                // HTML_PARSE_NOIMPLIED = 8192, // Do not add implied html/body... elements
                // HTML_PARSE_COMPACT = 65536, // compact small text nodes
                // HTML_PARSE_IGNORE_ENC = 2097152, // ignore internal document encoding hint
}

///Parser Errors
pub enum XmlParseError {
  ///Parsing returned a null pointer as document pointer
  GotNullPointer,
  ///Could not open file error.
  FileOpenError,
  ///Document too large for libxml2.
  DocumentTooLarge,
}

impl fmt::Debug for XmlParseError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match *self {
      XmlParseError::GotNullPointer => write!(f, "Got a Null pointer"),
      XmlParseError::FileOpenError => write!(f, "Unable to open path to file."),
      XmlParseError::DocumentTooLarge => write!(f, "Document too large for i32."),
    }
  }
}

/// Default encoding when not provided.
const DEFAULT_ENCODING: *const c_char = ptr::null();

/// Default URL when not provided.
const DEFAULT_URL: *const c_char = ptr::null();

/// Open file function.
fn xml_open(filename: &str) -> io::Result<*mut c_void> {
  let ptr = Box::into_raw(Box::new(fs::File::open(filename)?));
  Ok(ptr as *mut c_void)
}

/// Read callback for an FS file.
unsafe extern "C" fn xml_read(context: *mut c_void, buffer: *mut c_char, len: c_int) -> c_int {
  // Len is always positive, typically 40-4000 bytes.
  let file = context as *mut fs::File;
  let buf = slice::from_raw_parts_mut(buffer as *mut u8, len as usize);
  match io::Read::read(&mut *file, buf) {
    Ok(v) => v as c_int,
    Err(_) => -1,
  }
}

type XmlReadCallback = unsafe extern "C" fn(*mut c_void, *mut c_char, c_int) -> c_int;

/// Close callback for an FS file.
unsafe extern "C" fn xml_close(context: *mut c_void) -> c_int {
  // Take rust ownership of the context and then drop it.
  let file = context as *mut fs::File;
  let _ = Box::from_raw(file);
  0
}

type XmlCloseCallback = unsafe extern "C" fn(*mut c_void) -> c_int;

///Convert usize to i32 safely.
fn try_usize_to_i32(value: usize) -> Result<i32, XmlParseError> {
  if cfg!(target_pointer_width = "16") {
    // Cannot safely use our value comparison, but the conversion if always safe.
    Ok(value as i32)
  } else {
    if value < i32::max_value() as usize {
      // If the value can be safely represented as a 32-bit signed integer.
      Ok(value as i32)
    } else {
      // Document too large, cannot parse using libxml2.
      Err(XmlParseError::DocumentTooLarge)
    }
  }
}

#[derive(PartialEq)]
/// Enum for the parse formats supported by libxml2
pub enum ParseFormat {
  /// Strict parsing for XML
  XML,
  /// Relaxed parsing for HTML
  HTML,
}
/// Parsing API wrapper for libxml2
pub struct Parser {
  /// The `ParseFormat` for this parser
  pub format: ParseFormat,
}
impl Default for Parser {
  /// Create a parser for XML documents
  fn default() -> Self {
    Parser {
      format: ParseFormat::XML,
    }
  }
}
impl Parser {
  /// Create a parser for HTML documents
  pub fn default_html() -> Self {
    Parser {
      format: ParseFormat::HTML,
    }
  }

  /// Parses the XML/HTML file `filename` to generate a new `Document`
  pub fn parse_file(&self, filename: &str) -> Result<Document, XmlParseError> {
    self.parse_file_with_encoding(filename, None)
  }

  /// Parses the XML/HTML file `filename` with a manually-specified encoding
  /// to generate a new `Document`
  pub fn parse_file_with_encoding(
    &self,
    filename: &str,
    encoding: Option<&str>,
  ) -> Result<Document, XmlParseError> {
    // Create extern C callbacks for to read and close a Rust file through
    // a void pointer.
    let ioread: Option<XmlReadCallback> = Some(xml_read);
    let ioclose: Option<XmlCloseCallback> = Some(xml_close);
    let ioctx = match xml_open(filename) {
      Ok(v) => v,
      Err(_) => return Err(XmlParseError::FileOpenError),
    };

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;

    unsafe {
      xmlKeepBlanksDefault(1);
    }
    match self.format {
      ParseFormat::XML => {
        let options: i32 = XmlParserOption::Recover as i32
          + XmlParserOption::Noerror as i32
          + XmlParserOption::Nowarning as i32;
        unsafe {
          let doc_ptr = xmlReadIO(ioread, ioclose, ioctx, url_ptr, encoding_ptr, options);
          if doc_ptr.is_null() {
            Err(XmlParseError::GotNullPointer)
          } else {
            Ok(Document::new_ptr(doc_ptr))
          }
        }
      }
      ParseFormat::HTML => {
        // TODO: Allow user-specified options later on
        let options: i32 = HtmlParserOption::Recover as i32
          + HtmlParserOption::Noerror as i32
          + HtmlParserOption::Nowarning as i32;
        unsafe {
          let doc_ptr = htmlReadIO(ioread, ioclose, ioctx, url_ptr, encoding_ptr, options);
          if doc_ptr.is_null() {
            Err(XmlParseError::GotNullPointer)
          } else {
            Ok(Document::new_ptr(doc_ptr))
          }
        }
      }
    }
  }

  ///Parses the XML/HTML bytes `input` to generate a new `Document`
  pub fn parse_string<Bytes: AsRef<[u8]>>(&self, input: Bytes) -> Result<Document, XmlParseError> {
    self.parse_string_with_encoding(input, None)
  }

  ///Parses the XML/HTML bytes `input` with a manually-specified
  ///encoding to generate a new `Document`
  pub fn parse_string_with_encoding<Bytes: AsRef<[u8]>>(
    &self,
    input: Bytes,
    encoding: Option<&str>,
  ) -> Result<Document, XmlParseError> {
    // Process input bytes.
    let input_bytes = input.as_ref();
    let input_ptr = input_bytes.as_ptr() as *const c_char;
    let input_len = try_usize_to_i32(input_bytes.len())?;

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;
    match self.format {
      ParseFormat::XML => unsafe {
        let options: i32 = XmlParserOption::Recover as i32
          + XmlParserOption::Noerror as i32
          + XmlParserOption::Nowarning as i32;
        let docptr = xmlReadMemory(input_ptr, input_len, url_ptr, encoding_ptr, options);
        if docptr.is_null() {
          Err(XmlParseError::GotNullPointer)
        } else {
          Ok(Document::new_ptr(docptr))
        }
      },
      ParseFormat::HTML => unsafe {
        let options: i32 = HtmlParserOption::Recover as i32
          + HtmlParserOption::Noerror as i32
          + HtmlParserOption::Nowarning as i32;
        let docptr = htmlReadMemory(input_ptr, input_len, url_ptr, encoding_ptr, options);
        if docptr.is_null() {
          Err(XmlParseError::GotNullPointer)
        } else {
          Ok(Document::new_ptr(docptr))
        }
      },
    }
  }

  /// Checks a string for well-formedness.
  pub fn is_well_formed_html<Bytes: AsRef<[u8]>>(&self, input: Bytes) -> bool {
    self.is_well_formed_html_with_encoding(input, None)
  }

  /// Checks a string for well-formedness with manually-specified encoding.
  /// IMPORTANT: This function is currently implemented in a HACKY way, to ignore invalid errors for HTML5 elements (such as <math>)
  ///            this means you should NEVER USE IT WHILE THREADING, it is CERTAIN TO BREAK
  ///
  /// Help is welcome in implementing it correctly.
  pub fn is_well_formed_html_with_encoding<Bytes: AsRef<[u8]>>(
    &self,
    input: Bytes,
    encoding: Option<&str>,
  ) -> bool {
    // Process input string.
    let input_bytes = input.as_ref();
    if input_bytes.is_empty() {
      return false;
    }
    let input_ptr = input_bytes.as_ptr() as *const c_char;
    let input_len = match try_usize_to_i32(input_bytes.len()) {
      Ok(v) => v,
      Err(_) => return false,
    };

    // Process encoding.
    let encoding_cstring: Option<CString> = encoding.map(|v| CString::new(v).unwrap());
    let encoding_ptr = match encoding_cstring {
      Some(v) => v.as_ptr(),
      None => DEFAULT_ENCODING,
    };

    // Process url.
    let url_ptr = DEFAULT_URL;
    // disable generic error lines from libxml2
    match self.format {
      ParseFormat::XML => false, // TODO: Add support for XML at some point
      ParseFormat::HTML => unsafe {
        let ctxt = htmlNewParserCtxt();
        setWellFormednessHandler(ctxt);
        let docptr = htmlCtxtReadMemory(ctxt, input_ptr, input_len, url_ptr, encoding_ptr, 10_596); // htmlParserOption = 4+32+64+256+2048+8192
        let well_formed_final = if htmlWellFormed(ctxt) {
          // Basic well-formedness passes, let's check if we have an <html> element as root too
          if !docptr.is_null() {
            let node_ptr = xmlDocGetRootElement(docptr);
            let name_ptr = xmlNodeGetName(node_ptr);
            if name_ptr.is_null() {
              false
            }
            //empty string
            else {
              let c_root_name = CStr::from_ptr(name_ptr);
              let root_name = str::from_utf8(c_root_name.to_bytes()).unwrap().to_owned();
              root_name == "html"
            }
          } else {
            false
          }
        } else {
          false
        };

        if !ctxt.is_null() {
          htmlFreeParserCtxt(ctxt);
        }
        if !docptr.is_null() {
          xmlFreeDoc(docptr);
        }
        well_formed_final
      },
    }
  }
}
<|endoftext|>