<commit_before>extern crate base64;
extern crate clap;
extern crate hyper;
extern crate hyper_rustls;
extern crate pem;
#[macro_use]
extern crate prettytable;
extern crate ring;
extern crate rustls;
extern crate serde_json;
extern crate tempfile;
#[macro_use]
extern crate tera;

extern crate ct_tools;

use ct_tools::{crtsh, letsencrypt};
use ct_tools::common::{Log, sha256_hex};
use ct_tools::ct::submit_cert_to_logs;
use ct_tools::google::fetch_trusted_ct_logs;
use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;

fn pems_to_chain(data: &str) -> Vec<Vec<u8>> {
    pem::parse_many(data)
        .into_iter()
        .filter(|p| p.tag == "CERTIFICATE")
        .map(|p| p.contents)
        .collect()
}

fn submit(paths: clap::Values, log_urls: Option<clap::Values>) {
    let mut http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );
    http_client.set_read_timeout(Some(Duration::from_secs(15)));

    let logs = match log_urls {
        Some(urls) => {
            urls.map(|url| {
                         Log {
                             url: url.to_string(),
                             description: url.to_string(),
                             is_google: false,
                         }
                     })
                .collect()
        }
        None => fetch_trusted_ct_logs(&http_client),
    };

    for path in paths {
        println!("Submitting {} ...", path);

        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let mut chain = pems_to_chain(&contents);
        if chain.len() == 1 {
            // TODO: There's got to be some way to do this ourselves, instead of using crt.sh as a
            // glorified AIA chaser.
            println!("Only one certificate in chain, using crt.sh to build a full chain ...");
            chain = match crtsh::build_chain_for_cert(&http_client, &chain[0]) {
                Some(c) => c,
                None => {
                    println!("Unable to build a chain");
                    continue;
                }
            }
        }
        let scts = submit_cert_to_logs(&http_client, &logs, &chain);

        println!("Find the cert on crt.sh: {}",
                 crtsh::url_for_cert(&chain[0]));
        let mut table = prettytable::Table::new();
        table.add_row(row!["Log", "SCT"]);
        for (log, sct) in scts {
            table.add_row(row![log.description, base64::encode(&sct.to_raw_bytes())]);
        }
        table.printstd();
        println!();
        println!();
    }
}

fn check(paths: clap::Values) {
    let http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );

    for path in paths {
        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let chain = pems_to_chain(&contents);

        let is_logged = crtsh::is_cert_logged(&http_client, &chain[0]);
        if is_logged {
            println!("{} was already logged", path);
        } else {
            println!("{} has not been logged", path);
        }
    }
}

struct HttpHandler {
    templates: tera::Tera,
    http_client: hyper::Client,
    logs: Vec<Log>,
}

impl hyper::server::Handler for HttpHandler {
    fn handle(&self, request: hyper::server::Request, response: hyper::server::Response) {
        let peer_certs = request
            .ssl::<hyper_rustls::WrappedStream>()
            .unwrap()
            .to_tls_stream()
            .get_session()
            .get_peer_certificates();

        let mut crtsh_url = None;
        if peer_certs.is_some() && request.method == hyper::method::Method::Post {
            if let Some(chain) = crtsh::build_chain_for_cert(&self.http_client,
                                                             &peer_certs.as_ref().unwrap()[0].0) {
                let scts = submit_cert_to_logs(&self.http_client, &self.logs, &chain);
                if !scts.is_empty() {
                    crtsh_url = Some(crtsh::url_for_cert(&chain[0]));
                    println!("Successfully submitted: {}", sha256_hex(&chain[0]));
                }
            }
        }

        let rendered_cert = peer_certs.map(|chain| {
            let mut tmp = tempfile::NamedTempFile::new().unwrap();
            tmp.write_all(&chain[0].0).unwrap();
            tmp.flush().unwrap();
            let stdout = Command::new("openssl")
                .arg("x509")
                .arg("-text")
                .arg("-noout")
                .arg("-inform")
                .arg("der")
                .arg("-in")
                .arg(tmp.path().as_os_str())
                .output()
                .unwrap()
                .stdout;
            String::from_utf8_lossy(&stdout).into_owned()
        });

        let mut context = tera::Context::new();
        context.add("cert", &rendered_cert);
        context.add("crtsh_url", &crtsh_url);
        response
            .send(self.templates
                      .render("home.html", &context)
                      .unwrap()
                      .as_bytes())
            .unwrap();
    }
}

struct NoVerificationCertificateVerifier;
impl rustls::ClientCertVerifier for NoVerificationCertificateVerifier {
    fn verify_client_cert(&self,
                          _: &rustls::RootCertStore,
                          _: &[rustls::Certificate])
                          -> Result<(), rustls::TLSError> {
        Ok(())
    }
}

fn server(local_dev: bool, domain: Option<&str>, letsencrypt_env: Option<&str>) {
    match (local_dev, domain, letsencrypt_env) {
        (true, Some(_), _) |
        (true, _, Some(_)) => {
            panic!("Can't use both --local-dev and --letsencrypt-env or --domain")
        }
        (_, Some(_), None) |
        (_, None, Some(_)) => {
            panic!("When using Let's Encrypt, must set both --letsencrypt-env and --domain")
        }
        (false, _, None) => panic!("Must set at least one of --local-dev or --letsencrypt-env"),
        _ => {}
    };

    let mut tls_config = rustls::ServerConfig::new();
    tls_config.client_auth_offer = true;
    if local_dev {
        // TODO: not all the details on the cert are perfect, but it's fine.
        let (cert, pkey) = letsencrypt::generate_temporary_cert("localhost");
        tls_config.set_single_cert(vec![letsencrypt::openssl_cert_to_rustls(&cert)],
                                   letsencrypt::openssl_pkey_to_rustls(&pkey));
    } else {
        let letsencrypt_url = match letsencrypt_env.unwrap() {
            "prod" => "https://acme-v01.api.letsencrypt.org/directory",
            "dev" => "https://acme-staging.api.letsencrypt.org/directory",
            _ => unreachable!(),
        };
        let cert_cache = letsencrypt::DiskCache::new(env::home_dir()
                                                         .unwrap()
                                                         .join(".ct-tools")
                                                         .join("certificates"));
        tls_config.cert_resolver =
            Box::new(letsencrypt::AutomaticCertResolver::new(letsencrypt_url,
                                                             vec![domain.unwrap().to_string()],
                                                             cert_cache));
    }
    tls_config.set_persistence(rustls::ServerSessionMemoryCache::new(1024));
    tls_config.ticketer = rustls::Ticketer::new();
    // Disable certificate verification. In any normal context, this would be horribly insecure!
    // However, all we're doing is taking the certs and then turning around and submitting them to
    // CT logs, so it doesn't matter if they're verified.
    tls_config
        .dangerous()
        .set_certificate_verifier(Box::new(NoVerificationCertificateVerifier));
    let tls_server = hyper_rustls::TlsServer { cfg: Arc::new(tls_config) };

    let mut http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );
    let logs = fetch_trusted_ct_logs(&http_client);
    http_client.set_write_timeout(Some(Duration::from_secs(5)));
    http_client.set_read_timeout(Some(Duration::from_secs(5)));
    let handler = HttpHandler {
        templates: compile_templates!("templates/*"),
        http_client: http_client,
        logs: logs,
    };

    let addr = if local_dev {
        "127.0.0.1:1337"
    } else {
        "0.0.0.0:443"
    };
    println!("Listening on https://{} ...", addr);
    hyper::Server::https(addr, tls_server)
        .unwrap()
        // If there aren't at least two threads, the Let's Encrypt integration will deadlock.
        .handle_threads(handler, 16)
        .unwrap();
}

fn main() {
    let matches = clap::App::new("ct-tools")
        .subcommand(clap::SubCommand::with_name("submit")
                        .about("Directly submits certificates to CT logs")
                        .arg(clap::Arg::with_name("path")
                                 .multiple(true)
                                 .required(true)
                                 .help("Path to certificate or chain"))
                        .arg(clap::Arg::with_name("log-url")
                                 .long("--log-url")
                                 .multiple(true)
                                 .help("Log URL to submit certificate to")))
        .subcommand(clap::SubCommand::with_name("check")
                        .about("Checks whether a certificate exists in CT logs")
                        .arg(clap::Arg::with_name("path")
                                 .multiple(true)
                                 .required(true)
                                 .help("Path to certificate or chain")))
        .subcommand(clap::SubCommand::with_name("server")
                        .about("Run an HTTPS server that submits client to CT logs")
                        .arg(clap::Arg::with_name("local-dev")
                                 .long("--local-dev")
                                 .help("Local development, do not obtain a certificate"))
                        .arg(clap::Arg::with_name("domain")
                                 .takes_value(true)
                                 .long("--domain")
                                 .help("Domain this is running as"))
                        .arg(clap::Arg::with_name("letsencrypt-env")
                                 .takes_value(true)
                                 .long("--letsencrypt-env")
                                 .possible_values(&["dev", "prod"])
                                 .help("Let's Encrypt environment to use")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("submit") {
        submit(matches.values_of("path").unwrap(),
               matches.values_of("log-url"));
    } else if let Some(matches) = matches.subcommand_matches("check") {
        check(matches.values_of("path").unwrap());
    } else if let Some(matches) = matches.subcommand_matches("server") {
        server(matches.is_present("local-dev"),
               matches.value_of("domain"),
               matches.value_of("letsencrypt-env"));
    }
}
<commit_msg>Don't print the crt.sh link if there's no scts<commit_after>extern crate base64;
extern crate clap;
extern crate hyper;
extern crate hyper_rustls;
extern crate pem;
#[macro_use]
extern crate prettytable;
extern crate ring;
extern crate rustls;
extern crate serde_json;
extern crate tempfile;
#[macro_use]
extern crate tera;

extern crate ct_tools;

use ct_tools::{crtsh, letsencrypt};
use ct_tools::common::{Log, sha256_hex};
use ct_tools::ct::submit_cert_to_logs;
use ct_tools::google::fetch_trusted_ct_logs;
use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;

fn pems_to_chain(data: &str) -> Vec<Vec<u8>> {
    pem::parse_many(data)
        .into_iter()
        .filter(|p| p.tag == "CERTIFICATE")
        .map(|p| p.contents)
        .collect()
}

fn submit(paths: clap::Values, log_urls: Option<clap::Values>) {
    let mut http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );
    http_client.set_read_timeout(Some(Duration::from_secs(15)));

    let logs = match log_urls {
        Some(urls) => {
            urls.map(|url| {
                         Log {
                             url: url.to_string(),
                             description: url.to_string(),
                             is_google: false,
                         }
                     })
                .collect()
        }
        None => fetch_trusted_ct_logs(&http_client),
    };

    for path in paths {
        println!("Submitting {} ...", path);

        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let mut chain = pems_to_chain(&contents);
        if chain.len() == 1 {
            // TODO: There's got to be some way to do this ourselves, instead of using crt.sh as a
            // glorified AIA chaser.
            println!("Only one certificate in chain, using crt.sh to build a full chain ...");
            chain = match crtsh::build_chain_for_cert(&http_client, &chain[0]) {
                Some(c) => c,
                None => {
                    println!("Unable to build a chain");
                    continue;
                }
            }
        }
        let scts = submit_cert_to_logs(&http_client, &logs, &chain);

        if !scts.is_empty() {
            println!("Find the cert on crt.sh: {}",
                     crtsh::url_for_cert(&chain[0]));
        }
        let mut table = prettytable::Table::new();
        table.add_row(row!["Log", "SCT"]);
        for (log, sct) in scts {
            table.add_row(row![log.description, base64::encode(&sct.to_raw_bytes())]);
        }
        table.printstd();
        println!();
        println!();
    }
}

fn check(paths: clap::Values) {
    let http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );

    for path in paths {
        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let chain = pems_to_chain(&contents);

        let is_logged = crtsh::is_cert_logged(&http_client, &chain[0]);
        if is_logged {
            println!("{} was already logged", path);
        } else {
            println!("{} has not been logged", path);
        }
    }
}

struct HttpHandler {
    templates: tera::Tera,
    http_client: hyper::Client,
    logs: Vec<Log>,
}

impl hyper::server::Handler for HttpHandler {
    fn handle(&self, request: hyper::server::Request, response: hyper::server::Response) {
        let peer_certs = request
            .ssl::<hyper_rustls::WrappedStream>()
            .unwrap()
            .to_tls_stream()
            .get_session()
            .get_peer_certificates();

        let mut crtsh_url = None;
        if peer_certs.is_some() && request.method == hyper::method::Method::Post {
            if let Some(chain) = crtsh::build_chain_for_cert(&self.http_client,
                                                             &peer_certs.as_ref().unwrap()[0].0) {
                let scts = submit_cert_to_logs(&self.http_client, &self.logs, &chain);
                if !scts.is_empty() {
                    crtsh_url = Some(crtsh::url_for_cert(&chain[0]));
                    println!("Successfully submitted: {}", sha256_hex(&chain[0]));
                }
            }
        }

        let rendered_cert = peer_certs.map(|chain| {
            let mut tmp = tempfile::NamedTempFile::new().unwrap();
            tmp.write_all(&chain[0].0).unwrap();
            tmp.flush().unwrap();
            let stdout = Command::new("openssl")
                .arg("x509")
                .arg("-text")
                .arg("-noout")
                .arg("-inform")
                .arg("der")
                .arg("-in")
                .arg(tmp.path().as_os_str())
                .output()
                .unwrap()
                .stdout;
            String::from_utf8_lossy(&stdout).into_owned()
        });

        let mut context = tera::Context::new();
        context.add("cert", &rendered_cert);
        context.add("crtsh_url", &crtsh_url);
        response
            .send(self.templates
                      .render("home.html", &context)
                      .unwrap()
                      .as_bytes())
            .unwrap();
    }
}

struct NoVerificationCertificateVerifier;
impl rustls::ClientCertVerifier for NoVerificationCertificateVerifier {
    fn verify_client_cert(&self,
                          _: &rustls::RootCertStore,
                          _: &[rustls::Certificate])
                          -> Result<(), rustls::TLSError> {
        Ok(())
    }
}

fn server(local_dev: bool, domain: Option<&str>, letsencrypt_env: Option<&str>) {
    match (local_dev, domain, letsencrypt_env) {
        (true, Some(_), _) |
        (true, _, Some(_)) => {
            panic!("Can't use both --local-dev and --letsencrypt-env or --domain")
        }
        (_, Some(_), None) |
        (_, None, Some(_)) => {
            panic!("When using Let's Encrypt, must set both --letsencrypt-env and --domain")
        }
        (false, _, None) => panic!("Must set at least one of --local-dev or --letsencrypt-env"),
        _ => {}
    };

    let mut tls_config = rustls::ServerConfig::new();
    tls_config.client_auth_offer = true;
    if local_dev {
        // TODO: not all the details on the cert are perfect, but it's fine.
        let (cert, pkey) = letsencrypt::generate_temporary_cert("localhost");
        tls_config.set_single_cert(vec![letsencrypt::openssl_cert_to_rustls(&cert)],
                                   letsencrypt::openssl_pkey_to_rustls(&pkey));
    } else {
        let letsencrypt_url = match letsencrypt_env.unwrap() {
            "prod" => "https://acme-v01.api.letsencrypt.org/directory",
            "dev" => "https://acme-staging.api.letsencrypt.org/directory",
            _ => unreachable!(),
        };
        let cert_cache = letsencrypt::DiskCache::new(env::home_dir()
                                                         .unwrap()
                                                         .join(".ct-tools")
                                                         .join("certificates"));
        tls_config.cert_resolver =
            Box::new(letsencrypt::AutomaticCertResolver::new(letsencrypt_url,
                                                             vec![domain.unwrap().to_string()],
                                                             cert_cache));
    }
    tls_config.set_persistence(rustls::ServerSessionMemoryCache::new(1024));
    tls_config.ticketer = rustls::Ticketer::new();
    // Disable certificate verification. In any normal context, this would be horribly insecure!
    // However, all we're doing is taking the certs and then turning around and submitting them to
    // CT logs, so it doesn't matter if they're verified.
    tls_config
        .dangerous()
        .set_certificate_verifier(Box::new(NoVerificationCertificateVerifier));
    let tls_server = hyper_rustls::TlsServer { cfg: Arc::new(tls_config) };

    let mut http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_rustls::TlsClient::new())
    );
    let logs = fetch_trusted_ct_logs(&http_client);
    http_client.set_write_timeout(Some(Duration::from_secs(5)));
    http_client.set_read_timeout(Some(Duration::from_secs(5)));
    let handler = HttpHandler {
        templates: compile_templates!("templates/*"),
        http_client: http_client,
        logs: logs,
    };

    let addr = if local_dev {
        "127.0.0.1:1337"
    } else {
        "0.0.0.0:443"
    };
    println!("Listening on https://{} ...", addr);
    hyper::Server::https(addr, tls_server)
        .unwrap()
        // If there aren't at least two threads, the Let's Encrypt integration will deadlock.
        .handle_threads(handler, 16)
        .unwrap();
}

fn main() {
    let matches = clap::App::new("ct-tools")
        .subcommand(clap::SubCommand::with_name("submit")
                        .about("Directly submits certificates to CT logs")
                        .arg(clap::Arg::with_name("path")
                                 .multiple(true)
                                 .required(true)
                                 .help("Path to certificate or chain"))
                        .arg(clap::Arg::with_name("log-url")
                                 .long("--log-url")
                                 .multiple(true)
                                 .help("Log URL to submit certificate to")))
        .subcommand(clap::SubCommand::with_name("check")
                        .about("Checks whether a certificate exists in CT logs")
                        .arg(clap::Arg::with_name("path")
                                 .multiple(true)
                                 .required(true)
                                 .help("Path to certificate or chain")))
        .subcommand(clap::SubCommand::with_name("server")
                        .about("Run an HTTPS server that submits client to CT logs")
                        .arg(clap::Arg::with_name("local-dev")
                                 .long("--local-dev")
                                 .help("Local development, do not obtain a certificate"))
                        .arg(clap::Arg::with_name("domain")
                                 .takes_value(true)
                                 .long("--domain")
                                 .help("Domain this is running as"))
                        .arg(clap::Arg::with_name("letsencrypt-env")
                                 .takes_value(true)
                                 .long("--letsencrypt-env")
                                 .possible_values(&["dev", "prod"])
                                 .help("Let's Encrypt environment to use")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("submit") {
        submit(matches.values_of("path").unwrap(),
               matches.values_of("log-url"));
    } else if let Some(matches) = matches.subcommand_matches("check") {
        check(matches.values_of("path").unwrap());
    } else if let Some(matches) = matches.subcommand_matches("server") {
        server(matches.is_present("local-dev"),
               matches.value_of("domain"),
               matches.value_of("letsencrypt-env"));
    }
}
<|endoftext|>


<commit_before>//! Oration: a Rocket/Elm self hosted commenting system for static sites.
//!
//! Inspired by ![Isso](https://posativ.org/isso/), which is a welcomed change from Disqus.
//! However, the codebase is unmaintained and ![security concerns](https://axiomatic.neophilus.net/posts/2017-04-16-from-disqus-to-isso.html) abound.
//! Oration aims to be a fast, lightweight and secure platform for your comments. Nothing more, but importantly, nothing less.

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![cfg_attr(feature="clippy", warn(missing_docs_in_private_items))]
#![cfg_attr(feature="clippy", warn(single_match_else))]

#![feature(plugin, custom_derive, use_extern_macros)]
#![plugin(rocket_codegen)]

// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]

extern crate chrono;
extern crate dotenv;
#[macro_use]
extern crate error_chain;
extern crate rand;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate serde_derive;
extern crate bincode;
#[macro_use]
extern crate diesel;
extern crate r2d2_diesel;
extern crate r2d2;
extern crate yansi;
extern crate petgraph;
//extern crate argon2rs;
extern crate lettre;
extern crate lettre_email;
extern crate crypto;
extern crate reqwest;
extern crate bloomfilter;
extern crate openssl_probe;
extern crate serde_yaml;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
extern crate regex;
#[macro_use(log)]
extern crate log;

/// Loads configuration data from disk.
mod config;
/// Handles the database connection pool.
mod db;
/// SQL <----> Rust inerop using Diesel.
mod models;
/// Verbose schema for the comment database.
mod schema;
/// Serves up static files through Rocket.
mod static_files;
/// Handles the error chain of the program.
mod errors;
/// Tests for the Rocket side of the app.
#[cfg(test)]
mod tests;
/// Sends notification emails to admin.
mod notify;
/// Houses Data Structures that are needed in multiple modules.
mod data;

use std::io;
use std::net::SocketAddr;
use rocket::State;
use rocket::http::Status;
use rocket::response::{status, Failure, NamedFile};
use rocket::request::Form;
use rocket_contrib::Json;
use models::preferences::Preference;
use models::comments::{self, InsertedComment, NestedComment, Comment, CommentEdits};
use models::threads;
use std::process;
use yansi::Paint;
use config::Config;
use crypto::digest::Digest;
use crypto::sha2::Sha224;
use data::{FormInput, FormEdit, AuthHash};

/// Serve up the index file. This is only useful for development. Should not be used in a release.
//TODO: Serve this some other way, we don't want oration doing this work.
#[get("/")]
fn index() -> io::Result<NamedFile> {
    NamedFile::open("public/index.html")
}

/// Process comment input from form.
#[post("/oration", data = "<comment>")]
fn new_comment(
    conn: db::Conn,
    comment: Result<Form<FormInput>, Option<String>>,
    config: State<Config>,
    remote_addr: SocketAddr,
) -> Result<Json<InsertedComment>, Failure> {
    match comment {
        Ok(f) => {
            //If the comment form data is valid, proceed to comment insertion
            let form = f.into_inner();
            let ip_addr = remote_addr.ip().to_string();
            //Get thread id from the db, create if needed
            match threads::gen_or_get_id(&conn, &config.host, &form.title, &form.path) {
                Ok(tid) => {
                    match Comment::insert(&conn, tid, &form, &ip_addr, config.nesting_limit) {
                        Err(err) => {
                            //Something went wrong, return a 500
                            log::warn!("{}", &err);
                            for e in err.iter().skip(1) {
                                log::warn!(
                                    "    {} {}",
                                    Paint::white("=> Caused by:"),
                                    Paint::red(&e)
                                );
                            }
                            return Err(Failure(Status::InternalServerError));
                        }
                        Ok(comment) => {
                            //All good, return the comment
                            //Send notification to admin
                            if config.notifications.new_comment {
                                match notify::send_notification(
                                    &form,
                                    &config.notifications,
                                    &config.host,
                                    &config.blog_name,
                                    &ip_addr,
                                ) {
                                    Ok(_) => {
                                        log::info!(
                                            "📧  {}",
                                            Paint::blue("New comment email notification sent.")
                                        )
                                    }
                                    Err(err) => {
                                        log::warn!("{}", &err);
                                        for e in err.iter().skip(1) {
                                            log::warn!(
                                                "    {} {}",
                                                Paint::white("=> Caused by:"),
                                                Paint::red(&e)
                                            );
                                        }
                                    }
                                }
                            }
                            return Ok(Json(comment));
                        }
                    }
                }
                Err(err) => {
                    //We didn't get the thread id
                    log::warn!("{}", &err);
                    for e in err.iter().skip(1) {
                        log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
                    }
                    match err {
                        errors::Error(errors::ErrorKind::PathCheckFailed, _) => {
                            //The requsted path doesn't exist on the server
                            //Most likely an attempt at injecting junk into the db through the post method
                            return Err(Failure(Status::Forbidden));
                        }
                        _ => return Err(Failure(Status::InternalServerError)),
                    }
                }
            }
        }
        Err(_) => {
            //The form request was malformed, 400
            return Err(Failure(Status::BadRequest));
        }
    }
}

/// Information sent to the client upon initialisation.
#[derive(Serialize)]
struct Initialise {
    /// The clients' ip address, hashed via Sha224.
    user_ip: String,
    /// The Sha224 hash of the blog author to distinguish the authority on this blog.
    blog_author: String,
    /// Time frame in which users can edit thier own comments.
    edit_timeout: f32,
}

/// Gets a Sha224 hash from a clients IP along with the blog's author hash.
#[get("/oration/init")]
fn initialise(remote_addr: SocketAddr, config: State<Config>) -> Json<Initialise> {

    let ip_addr = remote_addr.ip().to_string();
    // create a Sha224 object
    let mut hasher = Sha224::new();
    // write input message
    hasher.input_str(&ip_addr);

    let to_send = Initialise {
        user_ip: hasher.result_str(),
        blog_author: config.author.hash.to_owned(),
        edit_timeout: config.edit_timeout,
    };

    Json(to_send)
}

#[derive(FromForm)]
/// Used in conjuction with `/delete?` and `/edit?`.
struct CommentId {
    /// The id of the requested comment.
    id: i32,
}

/// Requests comment deletion from a user, may or may not actually delete the comment
/// based on a number of possibilities: authentication issues, over time, etc.
/// Secondarily, the method of deletion may differ. If the comment has children it
/// is not deleted entirely, but flagged so that the rest of the conversation is not
/// automatically pruned.
#[delete("/oration/delete?<identifier>")]
fn delete_comment<'d>(
    conn: db::Conn,
    config: State<Config>,
    identifier: CommentId,
    hash: AuthHash,
) -> Result<String, Failure> {
    if let Err(err) = comments::update_authorised(
        &conn,
        &hash,
        &identifier.id,
        &config.edit_timeout,
    )
    {
        log::warn!("{}", err);
        for e in err.iter().skip(1) {
            log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
        }
        return Err(Failure(Status::Unauthorized));
    };
    match Comment::delete(&conn, &identifier.id) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            Err(Failure(Status::NotFound))
        }
    }
}

/// Requests an update to a comment from a user, which may or may not occur
/// based on a number of possibilities: authentication issues, over time, etc.
#[post("/oration/edit?<identifier>", data = "<edits>")]
fn edit_comment(
    conn: db::Conn,
    config: State<Config>,
    identifier: CommentId,
    hash: AuthHash,
    edits: Result<Form<FormEdit>, Option<String>>,
    remote_addr: SocketAddr,
) -> Result<Json<CommentEdits>, Failure> {
    if let Err(err) = comments::update_authorised(
        &conn,
        &hash,
        &identifier.id,
        &config.edit_timeout,
    )
    {
        log::warn!("{}", err);
        for e in err.iter().skip(1) {
            log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
        }
        return Err(Failure(Status::Unauthorized));
    };
    match edits {
        Ok(f) => {
            //If the comment form data is valid, proceed to updating the comment
            let form = f.into_inner();
            let ip_addr = remote_addr.ip().to_string();
            match Comment::update(&conn, &identifier.id, &form, &ip_addr) {
                Ok(edits) => Ok(Json(edits)),
                Err(err) => {
                    log::warn!("{}", err);
                    for e in err.iter().skip(1) {
                        log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
                    }
                    Err(Failure(Status::NotFound))
                }
            }
        }
        Err(_) => {
            //The form request was malformed or not UTF8 encoded: 400
            Err(Failure(Status::BadRequest))
        }
    }
}

/// Likes a comment so long as the current user has not done so already.
#[post("/oration/like?<identifier>")]
fn like_comment(
    conn: db::Conn,
    identifier: CommentId,
    remote_addr: SocketAddr,
) -> Result<String, status::Custom<String>> {
    let ip_addr = remote_addr.ip().to_string();
    match Comment::vote(&conn, &identifier.id, &ip_addr, true) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            //TODO: A custom status with a Failure that doesn't require &'static strings
            Err(status::Custom(Status::Forbidden, identifier.id.to_string()))
        }
    }
}

/// Dislikes a comment so long as the current user has not done so already.
#[post("/oration/dislike?<identifier>")]
fn dislike_comment(
    conn: db::Conn,
    identifier: CommentId,
    remote_addr: SocketAddr,
) -> Result<String, status::Custom<String>> {
    let ip_addr = remote_addr.ip().to_string();
    match Comment::vote(&conn, &identifier.id, &ip_addr, false) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            Err(status::Custom(Status::Forbidden, identifier.id.to_string()))
        }
    }
}

/// Test function that returns the session hash from the database.
#[get("/oration/session")]
fn get_session(conn: db::Conn) -> String {
    match Preference::get_session(&conn) {
        Ok(s) => s,
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            err.to_string()
        }
    }
}

#[derive(FromForm)]
/// Used in conjuction with `/count?` and `/comments?`.
struct Post {
    /// Gets the url for the request.
    url: String,
}

#[derive(Serialize)]
/// Comments to frontend
struct PostComments {
    /// A nested set of comments.
    comments: Vec<NestedComment>,
}

/// Return a json block of comment data for the requested url.
#[get("/oration/comments?<post>")]
fn get_comments(conn: db::Conn, post: Post) -> Option<Json<PostComments>> {
    //TODO: The logic here may not 100%, need to consider / vs /index.* for example.
    match NestedComment::list(&conn, &post.url) {
        Ok(comments) => {
            //We now have a vector of comments
            let to_send = PostComments { comments: comments };
            Some(Json(to_send))
        }
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            None
        }
    }
}

/// Returns the comment count for a given post from the database.
#[get("/oration/count?<post>")]
fn get_comment_count(conn: db::Conn, post: Post) -> String {
    //TODO: The logic here may not 100%, need to consider / vs /index.* for example.
    match Comment::count(&conn, &post.url) {
        Ok(s) => s.to_string(),
        Err(err) => {
            log::warn!("{}", err);
            for e in err.iter().skip(1) {
                log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            err.to_string()
        }
    }
}

/// Ignite Rocket, connect to the database and start serving data.
/// Exposes a connection to the database so we can set the session on startup.
fn rocket() -> (rocket::Rocket, db::Conn, String) {
    //Load configuration data from disk
    let config = match Config::load() {
        Ok(c) => c,
        Err(ref err) => {
            println!("Error loading configuration: {}", err);
            for e in err.iter().skip(1) {
                println!("caused by: {}", e);
            }
            process::exit(1)
        }
    };
    let host = config.host.clone();
    let pool = db::init_pool();
    let conn = match pool.get() {
        Ok(p) => db::Conn(p),
        Err(err) => {
            println!("Could not connect to database: {}", err);
            process::exit(1)
        }
    };
    let rocket = rocket::ignite().manage(pool).manage(config).mount(
        "/",
        routes![
            index, //TODO: index and static_files should not be managed by oration
            static_files::files,
            new_comment,
            delete_comment,
            edit_comment,
            like_comment,
            dislike_comment,
            initialise,
            get_session,
            get_comment_count,
            get_comments,
        ],
    );

    (rocket, conn, host)
}

/// Application entry point.
fn main() {
    openssl_probe::init_ssl_cert_env_vars();

    //Initialise webserver routes and database connection pool
    let (rocket, conn, host) = rocket();

    //Set the session info in the database
    log::info!("💿  {}", Paint::purple("Saving session hash to database"));
    match Preference::set_session(&conn) {
        Ok(set) => {
            if !set {
                //TODO: This may need to be a crit as well. Unsure.
                log::warn!("Failed to set session hash");
            }
        }
        Err(err) => {
            log::error!("{}", err);
            for e in err.iter().skip(1) {
                log::error!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
            }
            process::exit(1);
        }
    };

    log::info!(
        "📢  {} {}",
        Paint::blue("Oration will serve comments to"),
        host
    );

    //Start the web service
    rocket.launch();
}
<commit_msg>Streamline error logging boilerplate. Closes #73<commit_after>//! Oration: a Rocket/Elm self hosted commenting system for static sites.
//!
//! Inspired by ![Isso](https://posativ.org/isso/), which is a welcomed change from Disqus.
//! However, the codebase is unmaintained and ![security concerns](https://axiomatic.neophilus.net/posts/2017-04-16-from-disqus-to-isso.html) abound.
//! Oration aims to be a fast, lightweight and secure platform for your comments. Nothing more, but importantly, nothing less.

#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![cfg_attr(feature = "clippy", warn(missing_docs_in_private_items))]
#![cfg_attr(feature = "clippy", warn(single_match_else))]
#![feature(plugin, custom_derive, use_extern_macros)]
#![plugin(rocket_codegen)]
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]

extern crate bincode;
extern crate chrono;
#[macro_use]
extern crate diesel;
extern crate dotenv;
#[macro_use]
extern crate error_chain;
extern crate petgraph;
extern crate r2d2;
extern crate r2d2_diesel;
extern crate rand;
extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate serde_derive;
extern crate yansi;
//extern crate argon2rs;
extern crate bloomfilter;
extern crate crypto;
extern crate itertools;
#[macro_use]
extern crate lazy_static;
extern crate lettre;
extern crate lettre_email;
#[macro_use(log)]
extern crate log;
extern crate openssl_probe;
extern crate regex;
extern crate reqwest;
extern crate serde_yaml;

/// Loads configuration data from disk.
mod config;
/// Handles the database connection pool.
mod db;
/// SQL <----> Rust inerop using Diesel.
mod models;
/// Verbose schema for the comment database.
mod schema;
/// Serves up static files through Rocket.
mod static_files;
/// Handles the error chain of the program.
mod errors;
/// Tests for the Rocket side of the app.
#[cfg(test)]
mod tests;
/// Sends notification emails to admin.
mod notify;
/// Houses Data Structures that are needed in multiple modules.
mod data;

use std::io;
use std::net::SocketAddr;
use rocket::State;
use rocket::http::Status;
use rocket::response::{status, Failure, NamedFile};
use rocket::request::Form;
use rocket_contrib::Json;
use models::preferences::Preference;
use models::comments::{self, Comment, CommentEdits, InsertedComment, NestedComment};
use models::threads;
use errors::Error;
use std::process;
use yansi::Paint;
use config::Config;
use crypto::digest::Digest;
use crypto::sha2::Sha224;
use data::{AuthHash, FormEdit, FormInput};

/// Serve up the index file. This is only useful for development. Should not be used in a release.
//TODO: Serve this some other way, we don't want oration doing this work.
#[get("/")]
fn index() -> io::Result<NamedFile> {
    NamedFile::open("public/index.html")
}

/// Process comment input from form.
#[post("/oration", data = "<comment>")]
fn new_comment(
    conn: db::Conn,
    comment: Result<Form<FormInput>, Option<String>>,
    config: State<Config>,
    remote_addr: SocketAddr,
) -> Result<Json<InsertedComment>, Failure> {
    match comment {
        Ok(f) => {
            //If the comment form data is valid, proceed to comment insertion
            let form = f.into_inner();
            let ip_addr = remote_addr.ip().to_string();
            //Get thread id from the db, create if needed
            match threads::gen_or_get_id(&conn, &config.host, &form.title, &form.path) {
                Ok(tid) => {
                    match Comment::insert(&conn, tid, &form, &ip_addr, config.nesting_limit) {
                        Err(err) => {
                            //Something went wrong, return a 500
                            print_errors(&err);
                            return Err(Failure(Status::InternalServerError));
                        }
                        Ok(comment) => {
                            //All good, return the comment
                            //Send notification to admin
                            if config.notifications.new_comment {
                                match notify::send_notification(
                                    &form,
                                    &config.notifications,
                                    &config.host,
                                    &config.blog_name,
                                    &ip_addr,
                                ) {
                                    Ok(_) => log::info!(
                                        "📧  {}",
                                        Paint::blue("New comment email notification sent.")
                                    ),
                                    Err(err) => {
                                        print_errors(&err);
                                    }
                                }
                            }
                            return Ok(Json(comment));
                        }
                    }
                }
                Err(err) => {
                    //We didn't get the thread id
                    print_errors(&err);
                    match err {
                        errors::Error(errors::ErrorKind::PathCheckFailed, _) => {
                            //The requsted path doesn't exist on the server
                            //Most likely an attempt at injecting junk into the db through the post method
                            return Err(Failure(Status::Forbidden));
                        }
                        _ => return Err(Failure(Status::InternalServerError)),
                    }
                }
            }
        }
        Err(_) => {
            //The form request was malformed, 400
            return Err(Failure(Status::BadRequest));
        }
    }
}

/// Information sent to the client upon initialisation.
#[derive(Serialize)]
struct Initialise {
    /// The clients' ip address, hashed via Sha224.
    user_ip: String,
    /// The Sha224 hash of the blog author to distinguish the authority on this blog.
    blog_author: String,
    /// Time frame in which users can edit thier own comments.
    edit_timeout: f32,
}

/// Gets a Sha224 hash from a clients IP along with the blog's author hash.
#[get("/oration/init")]
fn initialise(remote_addr: SocketAddr, config: State<Config>) -> Json<Initialise> {
    let ip_addr = remote_addr.ip().to_string();
    // create a Sha224 object
    let mut hasher = Sha224::new();
    // write input message
    hasher.input_str(&ip_addr);

    let to_send = Initialise {
        user_ip: hasher.result_str(),
        blog_author: config.author.hash.to_owned(),
        edit_timeout: config.edit_timeout,
    };

    Json(to_send)
}

#[derive(FromForm)]
/// Used in conjuction with `/delete?` and `/edit?`.
struct CommentId {
    /// The id of the requested comment.
    id: i32,
}

/// Requests comment deletion from a user, may or may not actually delete the comment
/// based on a number of possibilities: authentication issues, over time, etc.
/// Secondarily, the method of deletion may differ. If the comment has children it
/// is not deleted entirely, but flagged so that the rest of the conversation is not
/// automatically pruned.
#[delete("/oration/delete?<identifier>")]
fn delete_comment<'d>(
    conn: db::Conn,
    config: State<Config>,
    identifier: CommentId,
    hash: AuthHash,
) -> Result<String, Failure> {
    if let Err(err) =
        comments::update_authorised(&conn, &hash, &identifier.id, &config.edit_timeout)
    {
        print_errors(&err);
        return Err(Failure(Status::Unauthorized));
    };
    match Comment::delete(&conn, &identifier.id) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            print_errors(&err);
            Err(Failure(Status::NotFound))
        }
    }
}

/// Requests an update to a comment from a user, which may or may not occur
/// based on a number of possibilities: authentication issues, over time, etc.
#[post("/oration/edit?<identifier>", data = "<edits>")]
fn edit_comment(
    conn: db::Conn,
    config: State<Config>,
    identifier: CommentId,
    hash: AuthHash,
    edits: Result<Form<FormEdit>, Option<String>>,
    remote_addr: SocketAddr,
) -> Result<Json<CommentEdits>, Failure> {
    if let Err(err) =
        comments::update_authorised(&conn, &hash, &identifier.id, &config.edit_timeout)
    {
        print_errors(&err);
        return Err(Failure(Status::Unauthorized));
    };
    match edits {
        Ok(f) => {
            //If the comment form data is valid, proceed to updating the comment
            let form = f.into_inner();
            let ip_addr = remote_addr.ip().to_string();
            match Comment::update(&conn, &identifier.id, &form, &ip_addr) {
                Ok(edits) => Ok(Json(edits)),
                Err(err) => {
                    print_errors(&err);
                    Err(Failure(Status::NotFound))
                }
            }
        }
        Err(_) => {
            //The form request was malformed or not UTF8 encoded: 400
            Err(Failure(Status::BadRequest))
        }
    }
}

/// Likes a comment so long as the current user has not done so already.
#[post("/oration/like?<identifier>")]
fn like_comment(
    conn: db::Conn,
    identifier: CommentId,
    remote_addr: SocketAddr,
) -> Result<String, status::Custom<String>> {
    let ip_addr = remote_addr.ip().to_string();
    match Comment::vote(&conn, &identifier.id, &ip_addr, true) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            print_errors(&err);
            //TODO: A custom status with a Failure that doesn't require &'static strings
            Err(status::Custom(Status::Forbidden, identifier.id.to_string()))
        }
    }
}

/// Dislikes a comment so long as the current user has not done so already.
#[post("/oration/dislike?<identifier>")]
fn dislike_comment(
    conn: db::Conn,
    identifier: CommentId,
    remote_addr: SocketAddr,
) -> Result<String, status::Custom<String>> {
    let ip_addr = remote_addr.ip().to_string();
    match Comment::vote(&conn, &identifier.id, &ip_addr, false) {
        Ok(_) => Ok(identifier.id.to_string()),
        Err(err) => {
            print_errors(&err);
            Err(status::Custom(Status::Forbidden, identifier.id.to_string()))
        }
    }
}

/// Test function that returns the session hash from the database.
#[get("/oration/session")]
fn get_session(conn: db::Conn) -> String {
    match Preference::get_session(&conn) {
        Ok(s) => s,
        Err(err) => {
            print_errors(&err);
            err.to_string()
        }
    }
}

#[derive(FromForm)]
/// Used in conjuction with `/count?` and `/comments?`.
struct Post {
    /// Gets the url for the request.
    url: String,
}

#[derive(Serialize)]
/// Comments to frontend
struct PostComments {
    /// A nested set of comments.
    comments: Vec<NestedComment>,
}

/// Return a json block of comment data for the requested url.
#[get("/oration/comments?<post>")]
fn get_comments(conn: db::Conn, post: Post) -> Option<Json<PostComments>> {
    //TODO: The logic here may not 100%, need to consider / vs /index.* for example.
    match NestedComment::list(&conn, &post.url) {
        Ok(comments) => {
            //We now have a vector of comments
            let to_send = PostComments { comments: comments };
            Some(Json(to_send))
        }
        Err(err) => {
            print_errors(&err);
            None
        }
    }
}

/// Returns the comment count for a given post from the database.
#[get("/oration/count?<post>")]
fn get_comment_count(conn: db::Conn, post: Post) -> String {
    //TODO: The logic here may not 100%, need to consider / vs /index.* for example.
    match Comment::count(&conn, &post.url) {
        Ok(s) => s.to_string(),
        Err(err) => {
            print_errors(&err);
            err.to_string()
        }
    }
}

/// Prints the current error chain to the log file / stdio.
fn print_errors(err: &Error) {
    log::warn!("{}", err);
    for e in err.iter().skip(1) {
        log::warn!("    {} {}", Paint::white("=> Caused by:"), Paint::red(&e));
    }
}

/// Ignite Rocket, connect to the database and start serving data.
/// Exposes a connection to the database so we can set the session on startup.
fn rocket() -> (rocket::Rocket, db::Conn, String) {
    //Load configuration data from disk
    let config = match Config::load() {
        Ok(c) => c,
        Err(ref err) => {
            println!("Error loading configuration: {}", err);
            for e in err.iter().skip(1) {
                println!("caused by: {}", e);
            }
            process::exit(1)
        }
    };
    let host = config.host.clone();
    let pool = db::init_pool();
    let conn = match pool.get() {
        Ok(p) => db::Conn(p),
        Err(err) => {
            println!("Could not connect to database: {}", err);
            process::exit(1)
        }
    };
    let rocket = rocket::ignite().manage(pool).manage(config).mount(
        "/",
        routes![
            index, //TODO: index and static_files should not be managed by oration
            static_files::files,
            new_comment,
            delete_comment,
            edit_comment,
            like_comment,
            dislike_comment,
            initialise,
            get_session,
            get_comment_count,
            get_comments,
        ],
    );

    (rocket, conn, host)
}

/// Application entry point.
fn main() {
    openssl_probe::init_ssl_cert_env_vars();

    //Initialise webserver routes and database connection pool
    let (rocket, conn, host) = rocket();

    //Set the session info in the database
    log::info!("💿  {}", Paint::purple("Saving session hash to database"));
    match Preference::set_session(&conn) {
        Ok(set) => {
            if !set {
                //TODO: This may need to be a crit as well. Unsure.
                log::warn!("Failed to set session hash");
            }
        }
        Err(err) => {
            print_errors(&err);
            process::exit(1);
        }
    };

    log::info!(
        "📢  {} {}",
        Paint::blue("Oration will serve comments to"),
        host
    );

    //Start the web service
    rocket.launch();
}
<|endoftext|>


<commit_before>//
//  SOS: the Stupid Operating System
//  by Hawk Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015-2016 Hawk Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! # SOS kernel
//! This crate contains the kernel for SOS, the Stupid Operating System.
//!
//! # SOS: the Stupid Operating System
//! SOS is a simple, tiny toy OS implemented in Rust. It targets the x86,
//! x86_64, and ARM v7 CPU architectures.
//!
//! I'm writing this mostly for fun, to learn more about OS design and kernel
//! hacking, so don't expect anything new or exciting out of this project.
//!
//! SOS is copyright 2015-2016 Hawk Weisman, and is released under the terms
//! of the MIT license.

// #![crate_name = "sos_kernel"]
// #![crate_type = "staticlib"]

#![doc(html_root_url = "https://hawkw.github.io/sos-kernel/")]

#![feature(core_intrinsics)]
#![feature( lang_items, asm, naked_functions )]
#![feature( const_fn
          , slice_patterns
          , associated_consts
          , unique
          , type_ascription
          , custom_derive )]
#![feature(collections)]
#![feature(question_mark)]

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#![no_std]
#![no_main]

// -- non-SOS dependencies --------------------------------------------------
extern crate collections;
extern crate rlibc;
extern crate spin;

#[macro_use] extern crate lazy_static;
#[macro_use] extern crate bitflags;
#[macro_use] extern crate custom_derive;
// -- SOS dependencies ------------------------------------------------------
extern crate sos_alloc as alloc;

#[macro_use] extern crate sos_vga as vga;

#[macro_use] pub mod macros;
#[macro_use] pub mod memory;
#[macro_use] pub mod io;

pub mod util;
pub mod multiboot2;
pub mod elf;
pub mod arch;

// Since the test module contains lang items, it can't be compiled when
// running tests.
#[cfg(not(test))] pub mod panic;

use arch::cpu;
use memory::PAddr;

#[macro_use]
macro_rules! init_log {
    (f$dots:expr, $task:expr, $msg:expr) => (
        println!( "{task:<40}{res:>38}\n{msg:>.width$}"
                , task = format!("{:>.width$}", $task, width = $dots)
                , res = "[ FAIL ]"
                , msg = $msg
                , width = $dots + 1
                )
    );
    (fail: $dots:expr, $task:expr) => (
            println!( "{task:<40}{res:>38}"
                    , task = format!("{:>.width$}", $task, width = $dots)
                    , res = "[ FAIL ]"
                    )
    );
    (okay: $dots:expr, $task:expr, $msg:expr) => (
        println!( "{task:<40}{res:>38}\n{msg:>.width$}"
                , task = format!("{:>.width$}", $task, width = $dots)
                , res = "[ OKAY ]"
                , msg = $msg
                , width = $dots + 1
                )
    );
    (okay: $dots:expr, $task:expr) => (
            println!( "{task:<40}{res:>38}"
                    , task = format!("{:>.width$}", $task, width = $dots)
                    , res = "[ OKAY ]"
                    )
    );
}

macro_rules! init_try {
    ($dots:expr, $task:expr, $result:expr) => (
        match $result {
            Ok(value) => {
                println!( "{task:<40}{res:>38}"
                        , task = format!("{:>.width$}", $task, width = $dots)
                        , res = "[ OKAY ]"
                        );
                value
            }
          , Err(why) => {
                println!( "{task:<40}{res:>38}\n\n{msg:>.width$}"
                        , task = format!("{:>.width$}", $task, width = $dots)
                        , res = "[ FAIL ]"
                        , msg = why
                        , width = $dots + 1
                        );
                return $expr
            }
        }
    )
}

/// Kernel main loop
pub fn kernel_main() {
    let mut a_vec = collections::vec::Vec::<usize>::new();
    println!( "TEST: Created a vector in kernel space! {:?}", a_vec);
    a_vec.push(1);
    println!( "TEST: pushed to vec: {:?}", a_vec);
    a_vec.push(2);
    println!( "TEST: pushed to vec: {:?}", a_vec);
    loop {
        unsafe { asm!("int $0" :: "N" (0x80)) };
        println!("Test interrupt okay");
    }
}

/// Kernel initialization function called from ASM
///
/// The kernel main loop expects to be passed the address of a valid
/// Multiboot 2 info struct. It's the bootloader's responsibility to ensure
/// that this is passed in the correct register as expected by the calling
/// convention (`edi` on x86). If this isn't there, you can expect to have a
/// bad problem and not go to space today.
//  TODO: since multiboot2 is x86-specific, this needs to move to `arch`.
//  we then want the kernel entry point to be `arch_init`. we can then
//  call into `kernel_init`.
#[no_mangle]
pub extern "C" fn kernel_start(multiboot_addr: PAddr) {
    io::term::CONSOLE.lock().clear();

    println!("Hello from the kernel!");

    // -- jump to architecture-specific init ---------------------------------
    arch::arch_init(multiboot_addr);

    // -- initialize interrupts ----------------------------------------------
    unsafe {
        println!(" . Enabling interrupts:");
        cpu::interrupts::initialize();
        println!("{:<38}{:>40}", " . Enabling interrupts", "[ OKAY ]");
    };

    // -- initialize the heap ------------------------------------------------
    unsafe {
        println!( "{:<38}{:>40}\n \
                    . . Heap begins at {:#x} and ends at {:#x}"
                , " . Intializing heap"
                , memory::init_heap().unwrap_or("[ FAIL ]")
                , memory::HEAP_BASE
                , memory::HEAP_TOP);
    };
    // -- call into kernel main loop ------------------------------------------
    // (currently, this does nothing)
    kernel_main()
}
<commit_msg>[kernel] fix arrangement of imports in main.rs<commit_after>//
//  SOS: the Stupid Operating System
//  by Hawk Weisman (hi@hawkweisman.me)
//
//  Copyright (c) 2015-2016 Hawk Weisman
//  Released under the terms of the MIT license. See `LICENSE` in the root
//  directory of this repository for more information.
//
//! # SOS kernel
//! This crate contains the kernel for SOS, the Stupid Operating System.
//!
//! # SOS: the Stupid Operating System
//! SOS is a simple, tiny toy OS implemented in Rust. It targets the x86,
//! x86_64, and ARM v7 CPU architectures.
//!
//! I'm writing this mostly for fun, to learn more about OS design and kernel
//! hacking, so don't expect anything new or exciting out of this project.
//!
//! SOS is copyright 2015-2016 Hawk Weisman, and is released under the terms
//! of the MIT license.

// #![crate_name = "sos_kernel"]
// #![crate_type = "staticlib"]

#![doc(html_root_url = "https://hawkw.github.io/sos-kernel/")]

#![feature(core_intrinsics)]
#![feature( lang_items, asm, naked_functions )]
#![feature( const_fn
          , slice_patterns
          , associated_consts
          , unique
          , type_ascription
          , custom_derive )]
#![feature(collections)]
#![feature(question_mark)]

#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]

#![no_std]
#![no_main]

// -- non-SOS dependencies --------------------------------------------------
extern crate collections;
extern crate rlibc;
extern crate spin;

#[macro_use] extern crate lazy_static;
#[macro_use] extern crate bitflags;
#[macro_use] extern crate custom_derive;

// -- SOS dependencies ------------------------------------------------------
extern crate sos_alloc as alloc;

#[macro_use] extern crate sos_vga as vga;

#[macro_use] pub mod macros;
#[macro_use] pub mod memory;
#[macro_use] pub mod io;

pub mod util;
pub mod multiboot2;
pub mod elf;
pub mod arch;

// Since the test module contains lang items, it can't be compiled when
// running tests.
#[cfg(not(test))] pub mod panic;

use arch::cpu;
use memory::PAddr;

#[macro_use]
macro_rules! init_log {
    (f$dots:expr, $task:expr, $msg:expr) => (
        println!( "{task:<40}{res:>38}\n{msg:>.width$}"
                , task = format!("{:>.width$}", $task, width = $dots)
                , res = "[ FAIL ]"
                , msg = $msg
                , width = $dots + 1
                )
    );
    (fail: $dots:expr, $task:expr) => (
            println!( "{task:<40}{res:>38}"
                    , task = format!("{:>.width$}", $task, width = $dots)
                    , res = "[ FAIL ]"
                    )
    );
    (okay: $dots:expr, $task:expr, $msg:expr) => (
        println!( "{task:<40}{res:>38}\n{msg:>.width$}"
                , task = format!("{:>.width$}", $task, width = $dots)
                , res = "[ OKAY ]"
                , msg = $msg
                , width = $dots + 1
                )
    );
    (okay: $dots:expr, $task:expr) => (
            println!( "{task:<40}{res:>38}"
                    , task = format!("{:>.width$}", $task, width = $dots)
                    , res = "[ OKAY ]"
                    )
    );
}

macro_rules! init_try {
    ($dots:expr, $task:expr, $result:expr) => (
        match $result {
            Ok(value) => {
                println!( "{task:<40}{res:>38}"
                        , task = format!("{:>.width$}", $task, width = $dots)
                        , res = "[ OKAY ]"
                        );
                value
            }
          , Err(why) => {
                println!( "{task:<40}{res:>38}\n\n{msg:>.width$}"
                        , task = format!("{:>.width$}", $task, width = $dots)
                        , res = "[ FAIL ]"
                        , msg = why
                        , width = $dots + 1
                        );
                return $expr
            }
        }
    )
}

/// Kernel main loop
pub fn kernel_main() {
    let mut a_vec = collections::vec::Vec::<usize>::new();
    println!( "TEST: Created a vector in kernel space! {:?}", a_vec);
    a_vec.push(1);
    println!( "TEST: pushed to vec: {:?}", a_vec);
    a_vec.push(2);
    println!( "TEST: pushed to vec: {:?}", a_vec);
    loop {
        unsafe { asm!("int $0" :: "N" (0x80)) };
        println!("Test interrupt okay");
    }
}

/// Kernel initialization function called from ASM
///
/// The kernel main loop expects to be passed the address of a valid
/// Multiboot 2 info struct. It's the bootloader's responsibility to ensure
/// that this is passed in the correct register as expected by the calling
/// convention (`edi` on x86). If this isn't there, you can expect to have a
/// bad problem and not go to space today.
//  TODO: since multiboot2 is x86-specific, this needs to move to `arch`.
//  we then want the kernel entry point to be `arch_init`. we can then
//  call into `kernel_init`.
#[no_mangle]
pub extern "C" fn kernel_start(multiboot_addr: PAddr) {
    io::term::CONSOLE.lock().clear();

    println!("Hello from the kernel!");

    // -- jump to architecture-specific init ---------------------------------
    arch::arch_init(multiboot_addr);

    // -- initialize interrupts ----------------------------------------------
    unsafe {
        println!(" . Enabling interrupts:");
        cpu::interrupts::initialize();
        println!("{:<38}{:>40}", " . Enabling interrupts", "[ OKAY ]");
    };

    // -- initialize the heap ------------------------------------------------
    unsafe {
        println!( "{:<38}{:>40}\n \
                    . . Heap begins at {:#x} and ends at {:#x}"
                , " . Intializing heap"
                , memory::init_heap().unwrap_or("[ FAIL ]")
                , memory::HEAP_BASE
                , memory::HEAP_TOP);
    };
    // -- call into kernel main loop ------------------------------------------
    // (currently, this does nothing)
    kernel_main()
}
<|endoftext|>


<commit_before>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Encapsulates has_more_command and avance method from the proposed API.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// Ignore comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					self.current_command = content;
					match self.command_type() {
						ACommand => {println!("ACommand: {0}", self.current_command);},
						CCommand => {println!("CCommand: {0}", self.current_command);},
						LCommand => {println!("LCommand: {0}", self.current_command);},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. for (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<commit_msg>des, comp and jump have been implemented. Test passes on 06/add/Add.asm<commit_after>use std::path::Path;
use std::fs::File;
use std::error::Error;
use std::env;
use std::io::BufReader;
use std::io::BufRead;
use std::io::Lines;

use CommandType::{ACommand, CCommand, LCommand};

enum CommandType {
	ACommand,
	CCommand,
	LCommand,
}

struct Parser {
	input_lines: Lines<BufReader<File>>,
	current_command: String,
}

impl Parser {
	/// Opens the input file/stream and gets ready to parse it.
	fn new(filename: &str) -> Parser {
		let path = Path::new(filename);
		let file = match File::open(&path) {
			Err(why) => panic!("couldn't open {}: {}", path.display(), Error::description(&why)),
			Ok(file) => file,
		};

		let lines = BufReader::new(file).lines(); // iterator
		Parser {
			input_lines: lines,
			current_command: String::new(),
		}
	}

	/// Encapsulates has_more_command and avance method from the proposed API.
	///
	///		Are there any more commands in the input?
	///			Reads the next command from the input and makes it the current command
	fn parse(&mut self) {
		loop {
			match self.input_lines.next() {
				Some(line) => {
					let content = line.unwrap().trim().to_string();

					// Ignore comments and empty lines
					if content.starts_with("//") || content == "\n" || content == "" {
						continue
					}

					self.current_command = content;
					match self.command_type() {
						ACommand => {
							println!("ACommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());
						},
						CCommand => {
							println!("CCommand: {0}", self.current_command);
							println!("\tdest: {0}", self.dest());
							println!("\tcomp: {0}", self.comp());
							println!("\tjump: {0}", self.jump());

						},
						LCommand => {
							println!("LCommand: {0}", self.current_command);
							println!("\tsymbol: {0}", self.symbol());

						},
					}
				},
				None => break,
			}
		}
	}

	/// Returns the type of the current command.
	///
	/// * ACommand: For @xxx where xxx is either a symbol or a decimal number
	/// * CCommand: For dest=comp;jump
	/// * LCommand: Pseudo-Command. For (xxx) where xxx is a symbol
	fn command_type(&self) -> CommandType {
		if self.current_command.starts_with("@") {
			return ACommand
		} else if self.current_command.starts_with("(") {
			return LCommand
		}
		CCommand
	}

	/// Returns the symbol or decimal xxx of the current command @xxx or (xxx).
	///
	/// Should be called only when command_type() is ACommand or LCommand.
	fn symbol(&self) -> String {
		let pattern: &[_] = &['(', ')', '@'];
		self.current_command.trim_matches(pattern).to_string()
	}

	/// Returns the dest mnemonic in the current CCommand. dest=comp;jump
	///
	///(8 posibilities.)
	/// Should only be called when cammand_type() is CCommand.
	fn dest(&self) -> String {
		// dest or jump field may be empy
		// if dest is empty, the '=' is omitted.
		// if jump is empty, the ';' is omitted.
		match self.current_command.contains('=') {
			true => {
				let v: Vec<&str> = self.current_command.split('=').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}

	// /// Returns the comp mnemonic in the current CCommand. dest=comp;jump
	// ///
	// /// 28 possibilities.
	// /// Should only be called when cammand_type() is CCommand.
	fn comp(&self) -> String {
		match self.current_command.contains('=') {
			true => {
				match self.current_command.contains(';') {
					true => { // dest=comp;jump
						let v: Vec<&str> = self.current_command.split(|c: char| c == ';' || c == '=' ).collect();
						v[1].to_string()
					},
					false => { // dest=comp
						let v: Vec<&str> = self.current_command.split('=').collect();
						v[1].to_string()
					}
				}
			},
			false => {
				match self.current_command.contains(';') {
					true => { // comp;jump
						let v: Vec<&str> = self.current_command.split(';').collect();
						v[0].to_string()
					},
					false => { // comp
						self.current_command.clone()
					},
				}
			}
		}
	}

	/// Returns the jump mnemonic in the current CCommand. dest=com;jump
	///
	/// 8-possibilities.
	/// Should only be called when cammand_type() is CCommand.
	fn jump(&self) -> String {
		match self.current_command.contains(';') {
			true => {
				let v: Vec<&str> = self.current_command.split(';').collect();
				return v[0].to_string()
			},
			false => "null".to_string(),
		}
	}
}

fn main() {
	let args: Vec<String> = env::args().collect();
	if args.len() != 2 {
		println!("HACK Assembler. Translates assembly (mnemonic) into binary machine code.\n\nUsage:\n\tassembler [PATH_TO_ASM_FILE]");
		return;
	}
	let path_to_asm = &args[1];
	let mut parser = Parser::new(path_to_asm);
	parser.parse();
}
<|endoftext|>


<commit_before>use std::env;
use std::fs::File;
use std::path::Path;

extern crate csv_checker;

fn report_errors_for_file(file: File) -> i32 {
    let errors: Vec<csv_checker::CSVError> = csv_checker::errors_for_csv(file);

    for error in errors.iter() {
        println!("error at line {}, col {}", error.line, error.col);
    }

    match errors.len() {
        0 => 0,
        _ => 1
    }
}

fn errors_for_args() -> i32 {
    let mut exit_code: i32 = 0;

    // Skip the first arg since it's the program itself
    let paths = env::args().skip(1);

    if paths.len() == 0 {
        panic!("Provide some paths!");
    }

    for arg in paths {
        println!("= {} =", arg);

        let path = Path::new(&arg);

        exit_code += match File::open(&path) {
            Err(_) => {
                println!("!!! Not found");
                1
            }
            Ok(file) => report_errors_for_file(file),
        };
    }

    exit_code
}

fn main() {
    let exit_code = errors_for_args();
    std::process::exit(exit_code);
}
<commit_msg>Remove inferrable type<commit_after>use std::env;
use std::fs::File;
use std::path::Path;

extern crate csv_checker;

fn report_errors_for_file(file: File) -> i32 {
    let errors = csv_checker::errors_for_csv(file);

    for error in errors.iter() {
        println!("error at line {}, col {}", error.line, error.col);
    }

    match errors.len() {
        0 => 0,
        _ => 1
    }
}

fn errors_for_args() -> i32 {
    let mut exit_code: i32 = 0;

    // Skip the first arg since it's the program itself
    let paths = env::args().skip(1);

    if paths.len() == 0 {
        panic!("Provide some paths!");
    }

    for arg in paths {
        println!("= {} =", arg);

        let path = Path::new(&arg);

        exit_code += match File::open(&path) {
            Err(_) => {
                println!("!!! Not found");
                1
            }
            Ok(file) => report_errors_for_file(file),
        };
    }

    exit_code
}

fn main() {
    let exit_code = errors_for_args();
    std::process::exit(exit_code);
}
<|endoftext|>


<commit_before>extern crate rustbox;
extern crate rustc_serialize;
extern crate gag;
extern crate toml;
extern crate itertools;

use std::env::args;
use std::path::Path;
use rustbox::{RustBox, Event, InputMode, InitOptions};
use rustbox::keyboard::Key;
use gag::Hold;

#[macro_use] mod signals;
mod config;
mod ui;
mod buffer;
mod util;
mod segment;

use ui::view::HexEdit;
use config::Config;

fn main() {
    let mut args = args();
    let config = match Config::open_default() {
        Ok(c) => c,
        Err(e) => {
            println!("Couldn't open config: {}", e);
            return;
        }
    };

    let mut edit = HexEdit::new(config);

    if args.len() > 1 {
        edit.open(&Path::new(&args.nth(1).unwrap()));
    }

    let hold = (Hold::stdout().unwrap(), Hold::stderr().unwrap());

    let rb = RustBox::init(InitOptions{
        buffer_stderr: false,
        input_mode: InputMode::Esc,
    }).unwrap();

    edit.resize(rb.width() as i32, rb.height() as i32);
    edit.draw(&rb);
    rb.present();
    loop {
        let event = rb.poll_event(false).unwrap();
        match event {
            // This case is here, since we want to have a 'way ouy' till we fixed bugs
            Event::KeyEvent(Some(Key::Ctrl('q'))) => break,
            Event::KeyEvent(Some(key)) => edit.input(key),
            Event::ResizeEvent(w, h) => { edit.resize(w, h) }
            _ => ()
        };
        rb.clear();
        edit.draw(&rb);
        rb.present();
    }
    drop(rb);
    drop(hold);
}
<commit_msg>Convert the main argument parsing to docopt<commit_after>extern crate rustbox;
extern crate rustc_serialize;
extern crate gag;
extern crate toml;
extern crate itertools;
extern crate docopt;

use std::path::Path;
use std::error::Error;
use std::io;
use std::io::Write;
use std::process;
use docopt::Docopt;
use rustbox::{RustBox, Event, InputMode, InitOptions};
use rustbox::keyboard::Key;
use gag::Hold;

#[macro_use] mod signals;
mod config;
mod ui;
mod buffer;
mod util;
mod segment;

use ui::view::HexEdit;
use config::Config;

static USAGE: &'static str = "
Usage: hyksa [options] [-C CONF... FILE]
       hyksa --help

Options:
    -h, --help                  Show this help message
    -c FILE, --config FILE      Use FILE as the config file
    -C CONF                     Set a configuration option, for example: -C line_width=16
";

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_FILE: Option<String>,
    flag_config: Option<String>,
    flag_C: Vec<String>,
}

fn exit_err<E: Error>(msg: &str, error: E) -> ! {
    write!(&mut io::stderr(), "{}: {}", msg, error).unwrap();
    process::exit(1);
}

fn main() {
    let args: Args = Docopt::new(USAGE).and_then(
        |d| d.decode()).unwrap_or_else(
        |e| e.exit());

    let config_res = if let Some(config_filename) = args.flag_config {
        Config::from_file(config_filename)
    } else {
        Config::open_default()
    };
    let mut config = config_res.unwrap_or_else(
        |e| exit_err("Couldn't open config file", e)
    );

    for config_line in &args.flag_C {
        config.set_from_string(config_line).unwrap_or_else(
            |e| exit_err("Couldn't parse command line config option", e)
        );
    }

    let mut edit = HexEdit::new(config);

    if let Some(ref filename) = args.arg_FILE {
        edit.open(&Path::new(filename));
    }

    let hold = (Hold::stdout().unwrap(), Hold::stderr().unwrap());

    let rb = RustBox::init(InitOptions{
        buffer_stderr: false,
        input_mode: InputMode::Esc,
    }).unwrap();

    edit.resize(rb.width() as i32, rb.height() as i32);
    edit.draw(&rb);
    rb.present();
    loop {
        let event = rb.poll_event(false).unwrap();
        match event {
            // This case is here, since we want to have a 'way ouy' till we fixed bugs
            Event::KeyEvent(Some(Key::Ctrl('q'))) => break,
            Event::KeyEvent(Some(key)) => edit.input(key),
            Event::ResizeEvent(w, h) => { edit.resize(w, h) }
            _ => ()
        };
        rb.clear();
        edit.draw(&rb);
        rb.present();
    }
    drop(rb);
    drop(hold);
}
<|endoftext|>


<commit_before>#![feature(fs_canonicalize)]

use std::path::Path;

#[cfg(not(test))]
fn main() {
    use std::{env, fs};
    use std::process::{self, Command};

    let path = env::current_exe().unwrap();
    let path = fs::canonicalize(path).unwrap();
    let path = path.parent().unwrap();
    let path = path.join("deps");

    let args = wrap_args(env::args(), path);
    let mut command = Command::new("cargo");
    command.args(&args);
    let mut child = command.spawn().unwrap_or_else(|e| panic!("{}", e));
    let exit_status = child.wait().unwrap_or_else(|e| panic!("{}", e));

    if let Some(code) = exit_status.code() {
        process::exit(code);
    }
}

fn wrap_args<T, I, P>(it: I, clippy_path: P) -> Vec<String>
    where T: AsRef<str>,
          I: IntoIterator<Item=T>,
          P: AsRef<Path> {

    let it = it.into_iter();
    let mut args = vec!["rustc".to_owned()];
    let mut has_double_hyphen = false;

    for arg in it.skip(2) {
        let arg = arg.as_ref().to_owned();
        has_double_hyphen |= &arg == "--";
        args.push(arg);
    }

    if !has_double_hyphen {
        args.push("--".to_owned());
    }
    args.push("-L".to_owned());
    args.push(clippy_path.as_ref().to_string_lossy().into_owned());
    args.push("-lclippy".to_owned());
    args.push("-Zextra-plugins=clippy".to_owned());
    args.push("-Zno-trans".to_owned());
    args
}
<commit_msg>simple update for nightly<commit_after>use std::path::Path;

#[cfg(not(test))]
fn main() {
    use std::{env, fs};
    use std::process::{self, Command};

    let path = env::current_exe().unwrap();
    let path = fs::canonicalize(path).unwrap();
    let path = path.parent().unwrap();
    let path = path.join("deps");

    let args = wrap_args(env::args(), path);
    let mut command = Command::new("cargo");
    command.args(&args);
    let mut child = command.spawn().unwrap_or_else(|e| panic!("{}", e));
    let exit_status = child.wait().unwrap_or_else(|e| panic!("{}", e));

    if let Some(code) = exit_status.code() {
        process::exit(code);
    }
}

fn wrap_args<T, I, P>(it: I, clippy_path: P) -> Vec<String>
    where T: AsRef<str>,
          I: IntoIterator<Item=T>,
          P: AsRef<Path> {

    let it = it.into_iter();
    let mut args = vec!["rustc".to_owned()];
    let mut has_double_hyphen = false;

    for arg in it.skip(2) {
        let arg = arg.as_ref().to_owned();
        has_double_hyphen |= &arg == "--";
        args.push(arg);
    }

    if !has_double_hyphen {
        args.push("--".to_owned());
    }
    args.push("-L".to_owned());
    args.push(clippy_path.as_ref().to_string_lossy().into_owned());
    args.push("-lclippy".to_owned());
    args.push("-Zextra-plugins=clippy".to_owned());
    args.push("-Zno-trans".to_owned());
    args
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate walkdir;
extern crate pulldown_cmark;
extern crate liquid;
extern crate image;
extern crate ftp;
extern crate checksums;
extern crate term_painter;
extern crate num_cpus;

mod config;
mod render;
mod template;
mod sync;
mod filter;
mod errors;

use std::fs;
use std::path::Path;
use clap::{App, Arg, SubCommand};
use config::Config;
use sync::Synchronizer;

fn build(path: &Path, conf: &Config) {
    if let Err(ref e) = render::build(path, conf) {
        errors::print_error(e);
    }
}

fn sync(path: &Path, conf: &Config, with_build: bool, overwrite: bool) {
    if with_build {
        build(path, conf);
    }
    let mut synchronizer = Synchronizer::new(conf);
    if overwrite {
        synchronizer.push_all_files();
    } else {
        synchronizer.execute();
    }
}

fn main() {
    // parse command line options
    //
    let matches = App::new("neptungen")
        .about("Simple website generator")
        .version("0.0.1")
        .arg(Arg::with_name("project_path")
            .short("p")
            .long("project_path")
            .help("Path to the root folder of the website project")
            .multiple(false)
            .takes_value(true))
        .subcommand(SubCommand::with_name("show_config")
            .about("Prints the configuration of the config.toml file"))
        .subcommand(SubCommand::with_name("build").about("Generate the website"))
        .subcommand(SubCommand::with_name("sync")
            .about("Used to synchronize the website with an ftp server")
            .arg(Arg::with_name("overwrite")
                .short("o")
                .long("overwrite")
                .help("Overwrites all remote files")
                .multiple(false)
                .takes_value(false))
            .arg(Arg::with_name("with_build")
                .short("n")
                .long("with_build")
                .help("Build the project before executing the sync")
                .multiple(false)
                .takes_value(false)))
        .get_matches();
    let path = if matches.is_present("project_path") {
        fs::canonicalize(matches.value_of("project_path").unwrap_or("."))
            .expect("could not determine path")
    } else {
        std::env::current_dir().unwrap_or(fs::canonicalize(".").expect("could not determine path"))
    };

    // load configuration from config.toml if present
    let conf = match Config::load(path.as_path()) {
        Ok(conf) => conf,
        Err(ref e) => {
            errors::print_error(e);
            Config::new(path.as_path())
        }
    };

    match matches.subcommand() {
        ("show_config", Some(_)) => {
            conf.print();
        }
        ("build", Some(_)) => {
            build(path.as_path(), &conf);
        }
        ("sync", Some(matches)) => {
            sync(path.as_path(),
                 &conf,
                 matches.is_present("with_build"),
                 matches.is_present("overwrite"))
        }
        _ => {
            println!("{}", matches.usage());
        }
    }
}<commit_msg>fix build error<commit_after>#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate walkdir;
extern crate pulldown_cmark;
extern crate liquid;
extern crate image;
extern crate ftp;
extern crate checksums;
extern crate term_painter;

mod config;
mod render;
mod template;
mod sync;
mod filter;
mod errors;

use std::fs;
use std::path::Path;
use clap::{App, Arg, SubCommand};
use config::Config;
use sync::Synchronizer;

fn build(path: &Path, conf: &Config) {
    if let Err(ref e) = render::build(path, conf) {
        errors::print_error(e);
    }
}

fn sync(path: &Path, conf: &Config, with_build: bool, overwrite: bool) {
    if with_build {
        build(path, conf);
    }
    let mut synchronizer = Synchronizer::new(conf);
    if overwrite {
        synchronizer.push_all_files();
    } else {
        synchronizer.execute();
    }
}

fn main() {
    // parse command line options
    //
    let matches = App::new("neptungen")
        .about("Simple website generator")
        .version("0.0.1")
        .arg(Arg::with_name("project_path")
            .short("p")
            .long("project_path")
            .help("Path to the root folder of the website project")
            .multiple(false)
            .takes_value(true))
        .subcommand(SubCommand::with_name("show_config")
            .about("Prints the configuration of the config.toml file"))
        .subcommand(SubCommand::with_name("build").about("Generate the website"))
        .subcommand(SubCommand::with_name("sync")
            .about("Used to synchronize the website with an ftp server")
            .arg(Arg::with_name("overwrite")
                .short("o")
                .long("overwrite")
                .help("Overwrites all remote files")
                .multiple(false)
                .takes_value(false))
            .arg(Arg::with_name("with_build")
                .short("n")
                .long("with_build")
                .help("Build the project before executing the sync")
                .multiple(false)
                .takes_value(false)))
        .get_matches();
    let path = if matches.is_present("project_path") {
        fs::canonicalize(matches.value_of("project_path").unwrap_or("."))
            .expect("could not determine path")
    } else {
        std::env::current_dir().unwrap_or(fs::canonicalize(".").expect("could not determine path"))
    };

    // load configuration from config.toml if present
    let conf = match Config::load(path.as_path()) {
        Ok(conf) => conf,
        Err(ref e) => {
            errors::print_error(e);
            Config::new(path.as_path())
        }
    };

    match matches.subcommand() {
        ("show_config", Some(_)) => {
            conf.print();
        }
        ("build", Some(_)) => {
            build(path.as_path(), &conf);
        }
        ("sync", Some(matches)) => {
            sync(path.as_path(),
                 &conf,
                 matches.is_present("with_build"),
                 matches.is_present("overwrite"))
        }
        _ => {
            println!("{}", matches.usage());
        }
    }
}<|endoftext|>