<commit_before>use std::borrow::Borrow;
use std::path::Path;
use std::sync::Arc;

#[cfg(target_os="linux")]
use std::process::{Command, Stdio};

use threadpool::ThreadPool;

use config::{Config, JiraConfig};
use git::Git;
use github;
use git_clone_manager::GitCloneManager;
use messenger;
use jira;
use slack::SlackAttachmentBuilder;
use worker;

#[cfg(target_os="linux")]
use docker;

pub fn comment_repo_version(version_script: &str,
                            jira_config: &JiraConfig,
                            jira: &jira::api::Session,
                            github: &github::api::Session,
                            clone_mgr: &GitCloneManager,
                            owner: &str,
                            repo: &str,
                            branch_name: &str,
                            commit_hash: &str,
                            commits: &Vec<github::PushCommit>) -> Result<(), String> {
    let held_clone_dir = try!(clone_mgr.clone(owner, repo));
    let clone_dir = held_clone_dir.dir();

    let git = Git::new(github.github_host(), github.github_token(), &clone_dir);

    // setup branch
    try!(git.checkout_branch(branch_name, commit_hash));

    let version = try!(run_script(version_script, clone_dir));

    let maybe_version = if version.len() > 0 {
        Some(version.as_str())
    } else {
        None
    };

    // resolve with version
    jira::workflow::resolve_issue(branch_name, maybe_version, commits, jira, jira_config);

    Ok(())
}

// Only run version scripts on Linux since firejail is only for Linux and it doesn't
// seem like a good idea to allow generic code execution without any containerization.
#[cfg(not(target_os="linux"))]
fn run_script(_: &str, _: &Path) -> Result<String, String> {
    return Err("Version scripts only supported when running Linux.".into());
}

#[cfg(target_os="linux")]
fn run_script(version_script: &str, clone_dir: &Path) -> Result<String, String> {
    debug!("Running version script: {}", version_script);
    let mut cmd = Command::new("firejail");
    cmd.arg("--quiet")
       .arg("--private=.")
       .arg("--private-etc=hostname alternatives")
       .arg("--net=none")
       .arg("--private-tmp")
       .arg("--private-dev")
       .arg("-c")
       .arg("bash")
       .arg("-c")
       .arg(version_script)
       .current_dir(clone_dir)
       .stdin(Stdio::null())
       .stderr(Stdio::piped())
       .stdout(Stdio::piped());

    if docker::in_docker() {
        // Otherwise we get "Warning: an existing sandbox was detected"
        // https://github.com/netblue30/firejail/issues/189
        cmd.arg("--force");
    } else {
        // couldn't get overlayfs to work inside docker
        cmd.arg("--overlay-tmpfs");
    }

    let child = match cmd.spawn() {
        Ok(c) => c,
        Err(e) => return Err(format!("Error starting version script (script: {}): {}", version_script, e)),
    };

    let result = match child.wait_with_output() {
        Ok(r) => r,
        Err(e) => return Err(format!("Error running version script (script: {}): {}", version_script, e)),
    };

    let mut output = String::new();
    if result.stdout.len() > 0 {
        let stdout = String::from_utf8_lossy(&result.stdout).into_owned();
        debug!("Version script stdout: \n---\n{}\n---", stdout);
        output += &stdout;
        // skip over firejail output (even with --quiet)
        if output.starts_with("OverlayFS") {
            let new_lines: Vec<String> =
                output.lines().skip(1)
                              .skip_while(|s| s.trim().len() == 0)
                              .map(|s| s.into())
                              .collect();
            output = new_lines.join("\n");
        }
    }

    let mut stderr = String::new();
    if result.stderr.len() > 0 {
        stderr = String::from_utf8_lossy(&result.stderr).into_owned();
        debug!("Version script stderr: \n---\n{}\n---", stderr);
    }

    if !result.status.success() {
        output += &stderr;
        Err(format!("Error running version script (exit code {}; script: {}):\n{}",
                    result.status.code().unwrap_or(-1),
                    version_script,
                    output))
    } else {

        Ok(output.trim().to_string())
    }
}

#[derive(Debug)]
pub struct RepoVersionRequest {
    pub repo: github::Repo,
    pub branch: String,
    pub commit_hash: String,
    pub commits: Vec<github::PushCommit>,
}

struct Runner {
    config: Arc<Config>,
    github_session: Arc<github::api::Session>,
    jira_session: Option<Arc<jira::api::Session>>,
    clone_mgr: Arc<GitCloneManager>,
    thread_pool: ThreadPool,
}

pub fn req(repo: &github::Repo, branch: &str, commit_hash: &str, commits: &Vec<github::PushCommit>) -> RepoVersionRequest {
    RepoVersionRequest {
        repo: repo.clone(),
        branch: branch.to_string(),
        commit_hash: commit_hash.to_string(),
        commits: commits.clone(),
    }
}

pub fn new_worker(max_concurrency: usize,
                  config: Arc<Config>,
                  github_session: Arc<github::api::Session>,
                  jira_session: Option<Arc<jira::api::Session>>,
                  clone_mgr: Arc<GitCloneManager>)
                   -> worker::Worker<RepoVersionRequest> {
    worker::Worker::new(Runner {
        config: config,
        github_session: github_session,
        jira_session: jira_session,
        clone_mgr: clone_mgr,
        thread_pool: ThreadPool::new(max_concurrency),
    })
}

impl worker::Runner<RepoVersionRequest> for Runner {
    fn handle(&self, req: RepoVersionRequest) {
        let github_session = self.github_session.clone();
        let jira_session = self.jira_session.clone();
        let clone_mgr = self.clone_mgr.clone();
        let config = self.config.clone();

        // launch another thread to do the version calculation
        self.thread_pool.execute(move || {
            let version_script;
            {
                let repos_lock = config.repos();
                version_script = repos_lock.version_script(&req.repo);
            }

            if let Some(version_script) = version_script {
                if let Some(ref jira_session) = jira_session {
                    if let Some(ref jira_config) = config.jira {
                        let jira = jira_session.borrow();
                        if let Err(e) = comment_repo_version(&version_script,
                                                             jira_config,
                                                             jira,
                                                             github_session.borrow(),
                                                             &clone_mgr,
                                                             &req.repo.owner.login(),
                                                             &req.repo.name,
                                                             &req.branch,
                                                             &req.commit_hash,
                                                             &req.commits) {
                            error!("Error running version script {}: {}", version_script, e);
                            let messenger = messenger::from_config(config.clone());

                            let attach = SlackAttachmentBuilder::new(&e)
                                .title(version_script.clone())
                                .color("danger")
                                .build();

                            messenger.send_to_channel("Error running version script", &vec![attach], &req.repo);

                            // resolve the issue with no version
                            jira::workflow::resolve_issue(&req.branch, None, &req.commits, jira, jira_config);
                        }
                    }
                }
            }
        });
    }
}

#[cfg(target_os="linux")]
#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::io::Write;

    extern crate tempdir;
    use self::tempdir::TempDir;

    #[test]
    fn test_run_script() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.sh");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(b"echo 1.2.3.4").expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("bash version.sh", &sub_dir));
    }

    #[test]
    fn test_run_python_script() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.py");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(b"print '1.2.3.4'").expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("python version.py", &sub_dir));
    }

    #[test]
    fn test_run_script_isolation() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let parent_file = dir.path().join("private.txt");
        {
            let mut file = fs::File::create(&parent_file).expect("create file");
            file.write_all(b"I am a file").expect("write file");
        }

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.sh");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(br#"
            # no `set -e` on purpose: try to do them all!
            rm ../private.txt
            rm version.sh
            touch ../muahaha.txt
            touch muahaha.txt

            echo 1.2.3.4
            "#).expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("bash version.sh", &sub_dir));

        assert!(parent_file.exists(), "version scripts should not be able to delete files outside its directory");
        assert!(!dir.path().join("muahaha.txt").exists(), "version scripts should not be able to create files outside its directory");

        if !docker::in_docker() {
            assert!(script_file.exists(), "version scripts should not be able to delete files inside its directory");
            assert!(!sub_dir.join("muahaha.txt").exists(), "version scripts should not be able to create files inside its directory");
        }
    }
}
<commit_msg>Fix version script overlayfs isolation (#103)<commit_after>use std::borrow::Borrow;
use std::path::Path;
use std::sync::Arc;

#[cfg(target_os="linux")]
use std::process::{Command, Stdio};

use threadpool::ThreadPool;

use config::{Config, JiraConfig};
use git::Git;
use github;
use git_clone_manager::GitCloneManager;
use messenger;
use jira;
use slack::SlackAttachmentBuilder;
use worker;

#[cfg(target_os="linux")]
use docker;

pub fn comment_repo_version(version_script: &str,
                            jira_config: &JiraConfig,
                            jira: &jira::api::Session,
                            github: &github::api::Session,
                            clone_mgr: &GitCloneManager,
                            owner: &str,
                            repo: &str,
                            branch_name: &str,
                            commit_hash: &str,
                            commits: &Vec<github::PushCommit>) -> Result<(), String> {
    let held_clone_dir = try!(clone_mgr.clone(owner, repo));
    let clone_dir = held_clone_dir.dir();

    let git = Git::new(github.github_host(), github.github_token(), &clone_dir);

    // setup branch
    try!(git.checkout_branch(branch_name, commit_hash));

    let version = try!(run_script(version_script, clone_dir));

    let maybe_version = if version.len() > 0 {
        Some(version.as_str())
    } else {
        None
    };

    // resolve with version
    jira::workflow::resolve_issue(branch_name, maybe_version, commits, jira, jira_config);

    Ok(())
}

// Only run version scripts on Linux since firejail is only for Linux and it doesn't
// seem like a good idea to allow generic code execution without any containerization.
#[cfg(not(target_os="linux"))]
fn run_script(_: &str, _: &Path) -> Result<String, String> {
    return Err("Version scripts only supported when running Linux.".into());
}

#[cfg(target_os="linux")]
fn run_script(version_script: &str, clone_dir: &Path) -> Result<String, String> {
    debug!("Running version script: {}", version_script);
    let mut cmd = Command::new("firejail");
    cmd.arg("--quiet")
       .arg("--private=.")
       .arg("--private-etc=hostname alternatives")
       .arg("--net=none")
       .arg("--private-tmp")
       .arg("--private-dev");

    if docker::in_docker() {
        // Otherwise we get "Warning: an existing sandbox was detected"
        // https://github.com/netblue30/firejail/issues/189
        cmd.arg("--force");
    } else {
        // couldn't get overlayfs to work inside docker
        cmd.arg("--overlay-tmpfs");
    }

    cmd.arg("-c")
       .arg("bash")
       .arg("-c")
       .arg(version_script)
       .current_dir(clone_dir)
       .stdin(Stdio::null())
       .stderr(Stdio::piped())
       .stdout(Stdio::piped());

    let child = match cmd.spawn() {
        Ok(c) => c,
        Err(e) => return Err(format!("Error starting version script (script: {}): {}", version_script, e)),
    };

    let result = match child.wait_with_output() {
        Ok(r) => r,
        Err(e) => return Err(format!("Error running version script (script: {}): {}", version_script, e)),
    };

    let mut output = String::new();
    if result.stdout.len() > 0 {
        let stdout = String::from_utf8_lossy(&result.stdout).into_owned();
        debug!("Version script stdout: \n---\n{}\n---", stdout);
        output += &stdout;
        // skip over firejail output (even with --quiet)
        if output.starts_with("OverlayFS") {
            let new_lines: Vec<String> =
                output.lines().skip(1)
                              .skip_while(|s| s.trim().len() == 0)
                              .map(|s| s.into())
                              .collect();
            output = new_lines.join("\n");
        }
    }

    let mut stderr = String::new();
    if result.stderr.len() > 0 {
        stderr = String::from_utf8_lossy(&result.stderr).into_owned();
        debug!("Version script stderr: \n---\n{}\n---", stderr);
    }

    if !result.status.success() {
        output += &stderr;
        Err(format!("Error running version script (exit code {}; script: {}):\n{}",
                    result.status.code().unwrap_or(-1),
                    version_script,
                    output))
    } else {

        Ok(output.trim().to_string())
    }
}

#[derive(Debug)]
pub struct RepoVersionRequest {
    pub repo: github::Repo,
    pub branch: String,
    pub commit_hash: String,
    pub commits: Vec<github::PushCommit>,
}

struct Runner {
    config: Arc<Config>,
    github_session: Arc<github::api::Session>,
    jira_session: Option<Arc<jira::api::Session>>,
    clone_mgr: Arc<GitCloneManager>,
    thread_pool: ThreadPool,
}

pub fn req(repo: &github::Repo, branch: &str, commit_hash: &str, commits: &Vec<github::PushCommit>) -> RepoVersionRequest {
    RepoVersionRequest {
        repo: repo.clone(),
        branch: branch.to_string(),
        commit_hash: commit_hash.to_string(),
        commits: commits.clone(),
    }
}

pub fn new_worker(max_concurrency: usize,
                  config: Arc<Config>,
                  github_session: Arc<github::api::Session>,
                  jira_session: Option<Arc<jira::api::Session>>,
                  clone_mgr: Arc<GitCloneManager>)
                   -> worker::Worker<RepoVersionRequest> {
    worker::Worker::new(Runner {
        config: config,
        github_session: github_session,
        jira_session: jira_session,
        clone_mgr: clone_mgr,
        thread_pool: ThreadPool::new(max_concurrency),
    })
}

impl worker::Runner<RepoVersionRequest> for Runner {
    fn handle(&self, req: RepoVersionRequest) {
        let github_session = self.github_session.clone();
        let jira_session = self.jira_session.clone();
        let clone_mgr = self.clone_mgr.clone();
        let config = self.config.clone();

        // launch another thread to do the version calculation
        self.thread_pool.execute(move || {
            let version_script;
            {
                let repos_lock = config.repos();
                version_script = repos_lock.version_script(&req.repo);
            }

            if let Some(version_script) = version_script {
                if let Some(ref jira_session) = jira_session {
                    if let Some(ref jira_config) = config.jira {
                        let jira = jira_session.borrow();
                        if let Err(e) = comment_repo_version(&version_script,
                                                             jira_config,
                                                             jira,
                                                             github_session.borrow(),
                                                             &clone_mgr,
                                                             &req.repo.owner.login(),
                                                             &req.repo.name,
                                                             &req.branch,
                                                             &req.commit_hash,
                                                             &req.commits) {
                            error!("Error running version script {}: {}", version_script, e);
                            let messenger = messenger::from_config(config.clone());

                            let attach = SlackAttachmentBuilder::new(&e)
                                .title(version_script.clone())
                                .color("danger")
                                .build();

                            messenger.send_to_channel("Error running version script", &vec![attach], &req.repo);

                            // resolve the issue with no version
                            jira::workflow::resolve_issue(&req.branch, None, &req.commits, jira, jira_config);
                        }
                    }
                }
            }
        });
    }
}

#[cfg(target_os="linux")]
#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::io::Write;

    extern crate tempdir;
    use self::tempdir::TempDir;

    #[test]
    fn test_run_script() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.sh");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(b"echo 1.2.3.4").expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("bash version.sh", &sub_dir));
    }

    #[test]
    fn test_run_python_script() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.py");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(b"print '1.2.3.4'").expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("python version.py", &sub_dir));
    }

    #[test]
    fn test_run_script_isolation() {
        let dir = TempDir::new("repo_version.rs").expect("create temp dir for repo_version.rs test");

        let parent_file = dir.path().join("private.txt");
        {
            let mut file = fs::File::create(&parent_file).expect("create file");
            file.write_all(b"I am a file").expect("write file");
        }

        let sub_dir = dir.path().join("subdir");
        fs::create_dir(&sub_dir).expect("create subdir");

        let script_file = sub_dir.join("version.sh");
        {
            let mut file = fs::File::create(&script_file).expect("create file");
            file.write_all(br#"
            # no `set -e` on purpose: try to do them all!
            rm ../private.txt
            rm version.sh
            touch ../muahaha.txt
            touch muahaha.txt

            echo 1.2.3.4
            "#).expect("write file");
        }

        assert_eq!(Ok("1.2.3.4".into()), run_script("bash version.sh", &sub_dir));

        assert!(parent_file.exists(), "version scripts should not be able to delete files outside its directory");
        assert!(!dir.path().join("muahaha.txt").exists(), "version scripts should not be able to create files outside its directory");

        if !docker::in_docker() {
            assert!(script_file.exists(), "version scripts should not be able to delete files inside its directory");
            assert!(!sub_dir.join("muahaha.txt").exists(), "version scripts should not be able to create files inside its directory");
        }
    }
}
<|endoftext|>


<commit_before>//! Repository interface

use self::super::Github;
use deployments::Deployments;
use keys::Keys;
use issues::{IssueRef, Issues};
use labels::Labels;
use pullrequests::PullRequests;
use releases::Releases;
use statuses::Statuses;

pub struct Repositories<'a> {
    github: &'a Github<'a>
}

impl <'a> Repositories<'a> {
    pub fn new(
        github: &'a Github<'a>) -> Repositories<'a> {
        Repositories {
            github: github
        }
    }
}

pub struct UserRepositories<'a> {
    github: &'a Github<'a>,
    owner: &'static str
}

impl <'a> UserRepositories<'a> {
    pub fn new(
        github: &'a Github<'a>,
        owner: &'static str) -> UserRepositories<'a> {
        UserRepositories {
            github: github,
            owner: owner
        }
    }
}

pub struct Repository<'a> {
  github: &'a Github<'a>,
  owner: &'static str,
  repo: &'static str
}

impl<'a> Repository<'a> {
  pub fn new(
    github: &'a Github<'a>, owner: &'static str, repo: &'static str) -> Repository<'a> {
    Repository {
      github: github,
      owner: owner,
      repo: repo
    }
  }

  /// get a reference to deployments associated with this repository ref
  pub fn deployments(&self) -> Deployments {
    Deployments::new(self.github, self.owner, self.repo)
  }

  /// get a reference deploy keys associated with this repository ref
  pub fn keys(&self) -> Keys {
    Keys::new(self.github, self.owner, self.repo)
  }

  /// get a reference to a specific github issue associated with this repoistory ref
  pub fn issue(&self, number: i64) -> IssueRef {
    IssueRef::new(self.github, self.owner, self.repo, number)
  }

  /// get a reference to github issues associated with this repoistory ref
  pub fn issues(&self) -> Issues {
    Issues::new(self.github, self.owner, self.repo)
  }

  /// get a list of labels associated with this repository ref
  pub fn labels(&self) -> Labels {
    Labels::new(self.github, self.owner, self.repo)
  }

  /// get a list of pulls associated with this repository ref
  pub fn pulls(&self) -> PullRequests {
    PullRequests::new(self.github, self.owner, self.repo)
  }

  /// get a reference to releases associated with this repository ref
  pub fn releases(&self) -> Releases {
    Releases::new(self.github, self.owner, self.repo)
  }

  /// get a references to statuses associated with this reposoitory ref
  pub fn statuses(&self) -> Statuses {
    Statuses::new(self.github, self.owner, self.repo)
  }
}
<commit_msg>alpha order<commit_after>//! Repository interface

use self::super::Github;
use deployments::Deployments;
use keys::Keys;
use issues::{IssueRef, Issues};
use labels::Labels;
use pullrequests::PullRequests;
use releases::Releases;
use statuses::Statuses;

pub struct Repositories<'a> {
    github: &'a Github<'a>
}

impl <'a> Repositories<'a> {
    pub fn new(
        github: &'a Github<'a>) -> Repositories<'a> {
        Repositories {
            github: github
        }
    }
}

pub struct UserRepositories<'a> {
    github: &'a Github<'a>,
    owner: &'static str
}

impl <'a> UserRepositories<'a> {
    pub fn new(
        github: &'a Github<'a>,
        owner: &'static str) -> UserRepositories<'a> {
        UserRepositories {
            github: github,
            owner: owner
        }
    }
}

pub struct Repository<'a> {
  github: &'a Github<'a>,
  owner: &'static str,
  repo: &'static str
}

impl<'a> Repository<'a> {
  pub fn new(
    github: &'a Github<'a>, owner: &'static str, repo: &'static str) -> Repository<'a> {
    Repository {
      github: github,
      owner: owner,
      repo: repo
    }
  }

  /// get a reference to deployments associated with this repository ref
  pub fn deployments(&self) -> Deployments {
    Deployments::new(self.github, self.owner, self.repo)
  }

  /// get a reference to a specific github issue associated with this repoistory ref
  pub fn issue(&self, number: i64) -> IssueRef {
    IssueRef::new(self.github, self.owner, self.repo, number)
  }

  /// get a reference to github issues associated with this repoistory ref
  pub fn issues(&self) -> Issues {
    Issues::new(self.github, self.owner, self.repo)
  }

  /// get a reference deploy keys associated with this repository ref
  pub fn keys(&self) -> Keys {
    Keys::new(self.github, self.owner, self.repo)
  }

  /// get a list of labels associated with this repository ref
  pub fn labels(&self) -> Labels {
    Labels::new(self.github, self.owner, self.repo)
  }

  /// get a list of pulls associated with this repository ref
  pub fn pulls(&self) -> PullRequests {
    PullRequests::new(self.github, self.owner, self.repo)
  }

  /// get a reference to releases associated with this repository ref
  pub fn releases(&self) -> Releases {
    Releases::new(self.github, self.owner, self.repo)
  }

  /// get a references to statuses associated with this reposoitory ref
  pub fn statuses(&self) -> Statuses {
    Statuses::new(self.github, self.owner, self.repo)
  }
}
<|endoftext|>


<commit_before>pub const RUNTIME: &'static str = r#"
#include "ruby.h"
#include <stdbool.h>
#include <string.h>

static void html_escaped_cat(VALUE str, char c) {
    switch (c) {
        case '\'':
            rb_str_cat_cstr(str, "'");
            break;
        case '&':
            rb_str_cat_cstr(str, "&");
            break;
        case '"':
            rb_str_cat_cstr(str, """);
            break;
        case '<':
            rb_str_cat_cstr(str, "<");
            break;
        case '>':
            rb_str_cat_cstr(str, ">");
            break;
    }
}

static VALUE optimized_escape_html(VALUE str) {
    long beg = 0;
    VALUE dest = 0;

    const long len = RSTRING_LEN(str);
    const char *cstr = RSTRING_PTR(str);

    for (long i = 0; i < len; i++) {
        switch (cstr[i]) {
          case '\'':
          case '&':
          case '"':
          case '<':
          case '>':
            if (!dest) {
                dest = rb_str_buf_new(len);
            }

            rb_str_cat(dest, cstr + beg, i - beg);
            beg = i + 1;

            html_escaped_cat(dest, cstr[i]);
            break;
        }
    }

    if (dest) {
        rb_str_cat(dest, cstr + beg, len - beg);
        return dest;
    } else {
        return str;
    }
}

static const char *DOT = ".";

static ID id_to_s;
static ID id_miss;

static VALUE cCGI;

struct path {
    char *keys[16];
    int length;
};

static VALUE fetch(VALUE context, const char *key) {
    if (strlen(key) == 1 && strncmp(key, DOT, 1) == 0) {
        return context;
    }

    switch (rb_type(context)) {
        case T_HASH: {
            VALUE miss = ID2SYM(id_miss);
            VALUE sym = ID2SYM(rb_intern(key));
            VALUE value = rb_hash_lookup2(context, sym, miss);
            if (value == miss) {
                VALUE key_str = rb_str_new_cstr(key);
                value = rb_hash_lookup2(context, key_str, miss);
                if (value == miss) {
                    value = Qundef;
                }
            }
            return value;
        }
        case T_FALSE:
            return Qfalse;
        case T_NIL:
        case T_UNDEF:
            return Qundef;
        default: {
            ID method = rb_intern(key);
            if (rb_respond_to(context, method)) {
                return rb_funcall(context, method, 0);
            } else {
                return Qundef;
            }
        }
    }
}

static VALUE context_fetch(VALUE stack, const char *key) {
    for (long i = RARRAY_LEN(stack) - 1; i >= 0; i--) {
        VALUE context = RARRAY_AREF(stack, i);
        VALUE value = fetch(context, key);
        if (value != Qundef) {
            return value;
        }
    }
    return Qundef;
}

static VALUE fetch_path(VALUE stack, const struct path *path) {
    VALUE value = context_fetch(stack, path->keys[0]);
    for (long i = 1; i < path->length; i++) {
        value = fetch(value, path->keys[i]);
    }
    return value;
}

static void append_value(VALUE buf, VALUE stack, const struct path *path, bool escape) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_NIL:
        case T_UNDEF:
            return;
        case T_STRING:
            break;
        default:
            value = rb_funcall(value, id_to_s, 0);
            break;
    }
    rb_str_buf_append(buf, escape ? optimized_escape_html(value) : value);
}

static void section(VALUE buf, VALUE stack, const struct path *path, void (*block)(VALUE, VALUE)) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_ARRAY:
            for (long i = 0; i < RARRAY_LEN(value); i++) {
                rb_ary_push(stack, RARRAY_AREF(value, i));
                block(buf, stack);
                rb_ary_pop(stack);
            }
            break;
        case T_NIL:
        case T_UNDEF:
        case T_FALSE:
            break;
        case T_TRUE:
            block(buf, stack);
            break;
        default:
            rb_ary_push(stack, value);
            block(buf, stack);
            rb_ary_pop(stack);
            break;
    }
}

static void inverted(VALUE buf, VALUE stack, const struct path *path, void (*block)(VALUE, VALUE)) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_ARRAY:
            if (RARRAY_LEN(value) == 0) {
                block(buf, stack);
            }
            break;
        case T_NIL:
        case T_UNDEF:
        case T_FALSE:
            block(buf, stack);
            break;
    }
}

static VALUE render(VALUE self, VALUE name, VALUE context);
static void initialize();

void Init_stache() {
    VALUE Stache = rb_define_module("Stache");
    VALUE Templates = rb_define_class_under(Stache, "Templates", rb_cObject);
    rb_define_singleton_method(Templates, "render", render, 2);

    rb_require("cgi");
    id_to_s = rb_intern("to_s");
    id_miss = rb_intern("__stache__miss__");
    initialize();
}
"#;
<commit_msg>Remove unused cgi variable<commit_after>pub const RUNTIME: &'static str = r#"
#include "ruby.h"
#include <stdbool.h>
#include <string.h>

static void html_escaped_cat(VALUE str, char c) {
    switch (c) {
        case '\'':
            rb_str_cat_cstr(str, "'");
            break;
        case '&':
            rb_str_cat_cstr(str, "&");
            break;
        case '"':
            rb_str_cat_cstr(str, """);
            break;
        case '<':
            rb_str_cat_cstr(str, "<");
            break;
        case '>':
            rb_str_cat_cstr(str, ">");
            break;
    }
}

static VALUE optimized_escape_html(VALUE str) {
    long beg = 0;
    VALUE dest = 0;

    const long len = RSTRING_LEN(str);
    const char *cstr = RSTRING_PTR(str);

    for (long i = 0; i < len; i++) {
        switch (cstr[i]) {
          case '\'':
          case '&':
          case '"':
          case '<':
          case '>':
            if (!dest) {
                dest = rb_str_buf_new(len);
            }

            rb_str_cat(dest, cstr + beg, i - beg);
            beg = i + 1;

            html_escaped_cat(dest, cstr[i]);
            break;
        }
    }

    if (dest) {
        rb_str_cat(dest, cstr + beg, len - beg);
        return dest;
    } else {
        return str;
    }
}

static const char *DOT = ".";

static ID id_to_s;
static ID id_miss;

struct path {
    char *keys[16];
    int length;
};

static VALUE fetch(VALUE context, const char *key) {
    if (strlen(key) == 1 && strncmp(key, DOT, 1) == 0) {
        return context;
    }

    switch (rb_type(context)) {
        case T_HASH: {
            VALUE miss = ID2SYM(id_miss);
            VALUE sym = ID2SYM(rb_intern(key));
            VALUE value = rb_hash_lookup2(context, sym, miss);
            if (value == miss) {
                VALUE key_str = rb_str_new_cstr(key);
                value = rb_hash_lookup2(context, key_str, miss);
                if (value == miss) {
                    value = Qundef;
                }
            }
            return value;
        }
        case T_FALSE:
            return Qfalse;
        case T_NIL:
        case T_UNDEF:
            return Qundef;
        default: {
            ID method = rb_intern(key);
            if (rb_respond_to(context, method)) {
                return rb_funcall(context, method, 0);
            } else {
                return Qundef;
            }
        }
    }
}

static VALUE context_fetch(VALUE stack, const char *key) {
    for (long i = RARRAY_LEN(stack) - 1; i >= 0; i--) {
        VALUE context = RARRAY_AREF(stack, i);
        VALUE value = fetch(context, key);
        if (value != Qundef) {
            return value;
        }
    }
    return Qundef;
}

static VALUE fetch_path(VALUE stack, const struct path *path) {
    VALUE value = context_fetch(stack, path->keys[0]);
    for (long i = 1; i < path->length; i++) {
        value = fetch(value, path->keys[i]);
    }
    return value;
}

static void append_value(VALUE buf, VALUE stack, const struct path *path, bool escape) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_NIL:
        case T_UNDEF:
            return;
        case T_STRING:
            break;
        default:
            value = rb_funcall(value, id_to_s, 0);
            break;
    }
    rb_str_buf_append(buf, escape ? optimized_escape_html(value) : value);
}

static void section(VALUE buf, VALUE stack, const struct path *path, void (*block)(VALUE, VALUE)) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_ARRAY:
            for (long i = 0; i < RARRAY_LEN(value); i++) {
                rb_ary_push(stack, RARRAY_AREF(value, i));
                block(buf, stack);
                rb_ary_pop(stack);
            }
            break;
        case T_NIL:
        case T_UNDEF:
        case T_FALSE:
            break;
        case T_TRUE:
            block(buf, stack);
            break;
        default:
            rb_ary_push(stack, value);
            block(buf, stack);
            rb_ary_pop(stack);
            break;
    }
}

static void inverted(VALUE buf, VALUE stack, const struct path *path, void (*block)(VALUE, VALUE)) {
    VALUE value = fetch_path(stack, path);
    switch (rb_type(value)) {
        case T_ARRAY:
            if (RARRAY_LEN(value) == 0) {
                block(buf, stack);
            }
            break;
        case T_NIL:
        case T_UNDEF:
        case T_FALSE:
            block(buf, stack);
            break;
    }
}

static VALUE render(VALUE self, VALUE name, VALUE context);
static void initialize();

void Init_stache() {
    VALUE Stache = rb_define_module("Stache");
    VALUE Templates = rb_define_class_under(Stache, "Templates", rb_cObject);
    rb_define_singleton_method(Templates, "render", render, 2);

    id_to_s = rb_intern("to_s");
    id_miss = rb_intern("__stache__miss__");
    initialize();
}
"#;
<|endoftext|>


<commit_before>#![feature(proc_macro)]
#[macro_use]
extern crate serde_derive;

extern crate futures;
extern crate tokio_core;

use std::thread;
use std::sync::mpsc::channel;
//use futures::stream::Stream;
//use tokio_core::reactor::Core;
//use tokio_core::net::TcpListener;
use std::net::TcpListener;
use std::collections::HashMap;
use std::io::Write;
//use futures::Future;
use std::sync::{Arc, Mutex};

mod userchannel;
mod messaging;

fn main() {
    let address = "127.0.0.1:8080";
    let listener = TcpListener::bind(&address).unwrap();
    let mut spawned = HashMap::new();
    let mut cnt = 0;

    let addr = listener.local_addr().unwrap();
    println!("Listening for connections on {}", addr);
    let (tx, rx) = channel();

    for socket in listener.incoming() {
        /* 1. Read incoming msg channel
         * 2. If it does not exist, spawn new thread
         * 3. Send socket to thread
         */
        if !spawned.contains_key("Channel") {
            let tx = tx.clone();
            thread::spawn(move|| {
                let (thr_tx, thr_rx) = channel();
                println!("Spawned: Sending socket");

                let mut channel_db = userchannel::ChannelDb{ channelname: "Rust".to_string(), users: HashMap::new(), values: Vec::new() };

                tx.send(thr_tx.clone()).unwrap();
                loop {
                    let mut thr_sock: std::net::TcpStream = thr_rx.recv().unwrap();
                    println!("Got thr sock");
                    thr_sock.write(b"Hello World\r\n").unwrap();
                }
            });
            let thr_feed = rx.recv().unwrap();
            println!("Got thr_feed");
            spawned.insert("Channel", thr_feed);
        }
        let thr_socket = spawned.get_mut("Channel").unwrap();
        thr_socket.send(socket.unwrap()).unwrap()
    }
}
<commit_msg>Support for echo to one user over telnet.<commit_after>#![feature(proc_macro)]
#[macro_use]
extern crate serde_derive;

extern crate futures;
extern crate tokio_core;

use std::thread;
use std::sync::mpsc::channel;
//use futures::stream::Stream;
//use tokio_core::reactor::Core;
//use tokio_core::net::TcpListener;
use std::net::TcpListener;
use std::collections::HashMap;
use std::io::Write;
//use std::io::Read;
use std::io;
use std::io::prelude::*;
use std::io::BufReader;
//use futures::Future;
use std::sync::{Arc, Mutex};

mod userchannel;
mod messaging;

fn main() {
    let address = "127.0.0.1:8080";
    let listener = TcpListener::bind(&address).unwrap();
    let mut spawned = HashMap::new();
    let mut cnt = 0;

    let addr = listener.local_addr().unwrap();
    println!("Listening for connections on {}", addr);
    let (tx, rx) = channel();

    for socket in listener.incoming() {
        /* 1. Read incoming msg channel
         * 2. If it does not exist, spawn new thread
         * 3. Send socket to thread
         */
        let mut result_str = String::new();
        let mut socket = socket.unwrap();
        let mut stream = BufReader::new(socket.try_clone().unwrap());
        let len  = stream.read_line(&mut result_str);
        println!("Got string {}", result_str);
        if !spawned.contains_key(&result_str) {
            let tx = tx.clone();
            thread::spawn(move|| {
                let (thr_tx, thr_rx) = channel();
                println!("Spawned: Sending socket");

                let mut channel_db = userchannel::ChannelDb{ channelname: "Rust".to_string(), users: HashMap::new(), values: Vec::new() };

                tx.send(thr_tx.clone()).unwrap();
                let mut thr_sock: std::net::TcpStream = thr_rx.recv().unwrap();
                println!("Got thr sock");
                loop {
                    let mut bufstr = String::new();
                    let mut stream = BufReader::new(thr_sock.try_clone().unwrap());
                    let len = stream.read_line(&mut bufstr);

                    thr_sock.write(&bufstr.into_bytes()).unwrap();
                }
            });
            let thr_feed = rx.recv().unwrap();
            println!("Got thr_feed");
            spawned.insert(result_str.clone(), thr_feed);
        }
        let thr_socket = spawned.get_mut(&result_str).unwrap();
        thr_socket.send(socket).unwrap()
    }
}
<|endoftext|>


<commit_before>use bencode::BEncode;
use std::path::PathBuf;
use std::collections::BTreeMap;
use std::{io, fs, fmt, cmp};
use sha1::Sha1;
use util::torrent_name;
use disk;

#[derive(Serialize, Deserialize, Clone)]
pub struct Info {
    pub name: String,
    pub announce: String,
    pub piece_len: u32,
    pub total_len: u64,
    pub hashes: Vec<Vec<u8>>,
    pub hash: [u8; 20],
    pub files: Vec<File>,
    pub private: bool,
}

impl fmt::Debug for Info {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Torrent Info {{ name: {:?}, announce: {:?}, piece_len: {:?}, total_len: {:?}, hash: {:?}, files: {:?} }}",
               self.name, self.announce, self.piece_len, self.total_len, torrent_name(&self.hash), self.files)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct File {
    pub path: PathBuf,
    pub length: usize,
}

impl File {
    fn from_bencode(data: BEncode) -> Result<File, &'static str> {
        let mut d = data.to_dict().ok_or("File must be a dictionary type!")?;
        match (d.remove("name"), d.remove("path"), d.remove("length")) {
            (Some(v), None, Some(l)) => {
                let f = File {
                    path: PathBuf::from(v.to_string().ok_or("Path must be a valid string.")?),
                    length: l.to_int().ok_or("File length must be a valid int")? as usize,
                };
                Ok(f)
            }
            (None, Some(path), Some(l)) => {
                let mut p = PathBuf::new();
                for dir in path.to_list().ok_or("File path should be a list")? {
                    p.push(dir.to_string().ok_or("File path parts should be strings")?);
                }
                let f = File {
                    path: p,
                    length: l.to_int().ok_or("File length must be a valid int")? as usize,
                };
                Ok(f)
            }
            _ => Err("File dict must contain length and name or path"),
        }
    }

    fn create(&self) -> Result<(), io::Error> {
        if let Some(parent) = self.path.parent() {
            fs::create_dir_all(parent)?;
        }
        let f = fs::OpenOptions::new().write(true).create(true).open(&self.path)?;
        f.set_len(self.length as u64)?;
        Ok(())
    }
}

impl Info {
    pub fn from_bencode(data: BEncode) -> Result<Info, &'static str> {
        data.to_dict()
            .and_then(|mut d| d.remove("info").and_then(|i| i.to_dict()).map(|i| (d, i)))
            .ok_or("")
            .and_then(|(mut d, mut i)| {
                let mut m = Sha1::new();
                let mut info_bytes = Vec::new();
                // TODO: Deal with this error/maybe convert everything to io::Error
                BEncode::Dict(i.clone()).encode(&mut info_bytes).unwrap();
                m.update(&info_bytes);
                let hash = m.digest().bytes();

                let a = d.remove("announce")
                    .and_then(|a| a.to_string())
                    .ok_or("Info must have announce url")?;
                let pl = i.remove("piece length")
                    .and_then(|i| i.to_int())
                    .ok_or("Info must specify piece length")?;
                let hashes = i.remove("pieces")
                    .and_then(|p| p.to_bytes())
                    .map(|mut p| {
                        let mut v = Vec::new();
                        while !p.is_empty() {
                            let remaining = p.split_off(20);
                            v.push(p);
                            p = remaining;
                        }
                        v
                    })
                .ok_or("Info must provide valid hashes")?;

                let private = i.remove("private")
                    .and_then(|v| v.to_int())
                    .map(|p| p == 1)
                    .unwrap_or(false);

                let files = parse_bencode_files(i)?;
                let name = if files.is_empty() {
                    files[0].path.clone().into_os_string().into_string().unwrap()
                } else if !files[0].path.has_root() {
                    let mut piter = files[0].path.components();
                    piter.next().unwrap().as_os_str().to_os_string().into_string().unwrap()
                } else {
                    unreachable!();
                };

                let total_len = files.iter().map(|f| f.length as u64).sum();
                Ok(Info {
                    name,
                    announce: a,
                    piece_len: pl as u32,
                    hashes,
                    hash,
                    files,
                    total_len,
                    private,
                })
            })

    }

    #[cfg(test)]
    pub fn with_pieces(pieces: usize) -> Info {
        Info {
            name: String::from(""),
            announce: String::from(""),
            piece_len: 16384,
            total_len: 16384 * pieces as u64,
            hashes: vec![vec![0u8]; pieces],
            hash: [0u8; 20],
            files: vec![],
            private: false,
        }
    }
    pub fn create_files(&self) -> Result<(), io::Error> {
        for file in self.files.iter() {
            file.create()?;
        }
        Ok(())
    }

    pub fn block_len(&self, idx: u32, offset: u32) -> u32 {
        if idx != self.pieces() - 1 {
            16384
        } else {
            let last_piece_len = (self.total_len - self.piece_len as u64 * (self.pieces() as u64 - 1)) as u32;
            // Note this is not the real last block len, just what it will be IF the offset really
            // is for the last block
            let last_block_len = last_piece_len - offset;
            if offset < last_piece_len && last_block_len <= 16384 {
                last_block_len
            } else {
                16384
            }
        }
    }

    pub fn piece_len(&self, idx: u32) -> u32 {
        if idx != self.pieces() - 1 {
            self.piece_len
        } else {
            (self.total_len - self.piece_len as u64 * (self.pieces() as u64 - 1)) as u32
        }
    }

    pub fn pieces(&self) -> u32 {
        self.hashes.len() as u32
    }

    /// Calculates the file offsets for a given block at index/begin
    pub fn block_disk_locs(&self, index: u32, begin: u32) -> Vec<disk::Location> {
        let len = self.block_len(index, begin);
        self.calc_disk_locs(index, begin, len)
    }

    /// Calculates the file offsets for a given piece at index
    pub fn piece_disk_locs(&self, index: u32) -> Vec<disk::Location> {
        let len = self.piece_len(index);
        self.calc_disk_locs(index, 0, len)
    }

    /// Calculates the file offsets for a given index, begin, and block length.
    fn calc_disk_locs(&self, index: u32, begin: u32, mut len: u32) -> Vec<disk::Location> {
        // The absolute byte offset where we start processing data.
        let mut cur_start = index * self.piece_len as u32 + begin;
        // Current index of the data block we're writing
        let mut data_start = 0;
        // The current file end length.
        let mut fidx = 0;
        // Iterate over all file lengths, if we find any which end a bigger
        // idx than cur_start, write from cur_start..cur_start + file_write_len for that file
        // and continue if we're now at the end of the file.
        let mut locs = Vec::new();
        for f in self.files.iter() {
            fidx += f.length;
            if (cur_start as usize) < fidx {
                let file_write_len = cmp::min(fidx - cur_start as usize, len as usize);
                let offset = (cur_start - (fidx - f.length) as u32) as u64;
                if file_write_len == len as usize {
                    // The file is longer than our len, just write to it,
                    // exit loop
                    locs.push(disk::Location::new(f.path.clone(), offset, data_start, data_start + file_write_len));
                    break;
                } else {
                    // Write to the end of file, continue
                    locs.push(disk::Location::new(f.path.clone(), offset, data_start, data_start + file_write_len as usize));
                    len -= file_write_len as u32;
                    cur_start += file_write_len as u32;
                    data_start += file_write_len;
                }
            }
        }
        locs
    }
}

fn parse_bencode_files(mut data: BTreeMap<String, BEncode>) -> Result<Vec<File>, &'static str> {
    match data.remove("files").and_then(|l| l.to_list()) {
        Some(fs) => {
            let mut path = PathBuf::new();
            path.push(data.remove("name")
                      .and_then(|v| v.to_string())
                      .ok_or("Multifile mode must have a name field")?);
            let mut files = Vec::new();
            for f in fs {
                let mut file = File::from_bencode(f)?;
                file.path = path.join(file.path);
                files.push(file);
            }
            Ok(files)
        }
        None => File::from_bencode(BEncode::Dict(data)).map(|f| vec![f]),
    }
}
<commit_msg>Cleanup info parsing logic<commit_after>use bencode::BEncode;
use std::path::PathBuf;
use std::collections::BTreeMap;
use std::{io, fs, fmt, cmp};
use sha1::Sha1;
use util::torrent_name;
use disk;

#[derive(Serialize, Deserialize, Clone)]
pub struct Info {
    pub name: String,
    pub announce: String,
    pub piece_len: u32,
    pub total_len: u64,
    pub hashes: Vec<Vec<u8>>,
    pub hash: [u8; 20],
    pub files: Vec<File>,
    pub private: bool,
}

impl fmt::Debug for Info {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Torrent Info {{ name: {:?}, announce: {:?}, piece_len: {:?}, total_len: {:?}, hash: {:?}, files: {:?} }}",
               self.name, self.announce, self.piece_len, self.total_len, torrent_name(&self.hash), self.files)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct File {
    pub path: PathBuf,
    pub length: usize,
}

impl File {
    fn from_bencode(data: BEncode) -> Result<File, &'static str> {
        let mut d = data.to_dict().ok_or("File must be a dictionary type!")?;
        match (d.remove("name"), d.remove("path"), d.remove("length")) {
            (Some(v), None, Some(l)) => {
                let f = File {
                    path: PathBuf::from(v.to_string().ok_or("Path must be a valid string.")?),
                    length: l.to_int().ok_or("File length must be a valid int")? as usize,
                };
                Ok(f)
            }
            (None, Some(path), Some(l)) => {
                let mut p = PathBuf::new();
                for dir in path.to_list().ok_or("File path should be a list")? {
                    p.push(dir.to_string().ok_or("File path parts should be strings")?);
                }
                let f = File {
                    path: p,
                    length: l.to_int().ok_or("File length must be a valid int")? as usize,
                };
                Ok(f)
            }
            _ => Err("File dict must contain length and name or path"),
        }
    }

    fn create(&self) -> Result<(), io::Error> {
        if let Some(parent) = self.path.parent() {
            fs::create_dir_all(parent)?;
        }
        let f = fs::OpenOptions::new().write(true).create(true).open(&self.path)?;
        f.set_len(self.length as u64)?;
        Ok(())
    }
}

impl Info {
    pub fn from_bencode(data: BEncode) -> Result<Info, &'static str> {
        data.to_dict()
            .and_then(|mut d| d.remove("info")
                                .and_then(|i| i.to_dict())
                                .map(|i| (d, i))
            )
            .ok_or("invalid info field")
            .and_then(|(mut d, mut i)| {
                let mut m = Sha1::new();
                let mut info_bytes = Vec::new();
                BEncode::Dict(i.clone()).encode(&mut info_bytes).unwrap();
                m.update(&info_bytes);
                let hash = m.digest().bytes();

                let a = d.remove("announce")
                    .and_then(|a| a.to_string())
                    .ok_or("Info must have announce url")?;
                let pl = i.remove("piece length")
                    .and_then(|i| i.to_int())
                    .ok_or("Info must specify piece length")?;
                let hashes = i.remove("pieces")
                    .and_then(|p| p.to_bytes())
                    .map(|mut p| {
                        let mut v = Vec::new();
                        while !p.is_empty() {
                            let remaining = p.split_off(20);
                            v.push(p);
                            p = remaining;
                        }
                        v
                    })
                .ok_or("Info must provide valid hashes")?;

                let private = i.remove("private")
                    .and_then(|v| v.to_int())
                    .map(|p| p == 1)
                    .unwrap_or(false);

                let files = parse_bencode_files(i)?;
                let name = if files.is_empty() {
                    files[0].path.clone().into_os_string().into_string().unwrap()
                } else if !files[0].path.has_root() {
                    let mut piter = files[0].path.components();
                    piter.next().unwrap().as_os_str().to_os_string().into_string().unwrap()
                } else {
                    unreachable!();
                };

                let total_len = files.iter().map(|f| f.length as u64).sum();
                Ok(Info {
                    name,
                    announce: a,
                    piece_len: pl as u32,
                    hashes,
                    hash,
                    files,
                    total_len,
                    private,
                })
            })
    }

    #[cfg(test)]
    pub fn with_pieces(pieces: usize) -> Info {
        Info {
            name: String::from(""),
            announce: String::from(""),
            piece_len: 16384,
            total_len: 16384 * pieces as u64,
            hashes: vec![vec![0u8]; pieces],
            hash: [0u8; 20],
            files: vec![],
            private: false,
        }
    }
    pub fn create_files(&self) -> Result<(), io::Error> {
        for file in self.files.iter() {
            file.create()?;
        }
        Ok(())
    }

    pub fn block_len(&self, idx: u32, offset: u32) -> u32 {
        if idx != self.pieces() - 1 {
            16384
        } else {
            let last_piece_len = (self.total_len - self.piece_len as u64 * (self.pieces() as u64 - 1)) as u32;
            // Note this is not the real last block len, just what it will be IF the offset really
            // is for the last block
            let last_block_len = last_piece_len - offset;
            if offset < last_piece_len && last_block_len <= 16384 {
                last_block_len
            } else {
                16384
            }
        }
    }

    pub fn piece_len(&self, idx: u32) -> u32 {
        if idx != self.pieces() - 1 {
            self.piece_len
        } else {
            (self.total_len - self.piece_len as u64 * (self.pieces() as u64 - 1)) as u32
        }
    }

    pub fn pieces(&self) -> u32 {
        self.hashes.len() as u32
    }

    /// Calculates the file offsets for a given block at index/begin
    pub fn block_disk_locs(&self, index: u32, begin: u32) -> Vec<disk::Location> {
        let len = self.block_len(index, begin);
        self.calc_disk_locs(index, begin, len)
    }

    /// Calculates the file offsets for a given piece at index
    pub fn piece_disk_locs(&self, index: u32) -> Vec<disk::Location> {
        let len = self.piece_len(index);
        self.calc_disk_locs(index, 0, len)
    }

    /// Calculates the file offsets for a given index, begin, and block length.
    fn calc_disk_locs(&self, index: u32, begin: u32, mut len: u32) -> Vec<disk::Location> {
        // The absolute byte offset where we start processing data.
        let mut cur_start = index * self.piece_len as u32 + begin;
        // Current index of the data block we're writing
        let mut data_start = 0;
        // The current file end length.
        let mut fidx = 0;
        // Iterate over all file lengths, if we find any which end a bigger
        // idx than cur_start, write from cur_start..cur_start + file_write_len for that file
        // and continue if we're now at the end of the file.
        let mut locs = Vec::new();
        for f in self.files.iter() {
            fidx += f.length;
            if (cur_start as usize) < fidx {
                let file_write_len = cmp::min(fidx - cur_start as usize, len as usize);
                let offset = (cur_start - (fidx - f.length) as u32) as u64;
                if file_write_len == len as usize {
                    // The file is longer than our len, just write to it,
                    // exit loop
                    locs.push(disk::Location::new(f.path.clone(), offset, data_start, data_start + file_write_len));
                    break;
                } else {
                    // Write to the end of file, continue
                    locs.push(disk::Location::new(f.path.clone(), offset, data_start, data_start + file_write_len as usize));
                    len -= file_write_len as u32;
                    cur_start += file_write_len as u32;
                    data_start += file_write_len;
                }
            }
        }
        locs
    }
}

fn parse_bencode_files(mut data: BTreeMap<String, BEncode>) -> Result<Vec<File>, &'static str> {
    match data.remove("files").and_then(|l| l.to_list()) {
        Some(fs) => {
            let mut path = PathBuf::new();
            path.push(data.remove("name")
                      .and_then(|v| v.to_string())
                      .ok_or("Multifile mode must have a name field")?);
            let mut files = Vec::new();
            for f in fs {
                let mut file = File::from_bencode(f)?;
                file.path = path.join(file.path);
                files.push(file);
            }
            Ok(files)
        }
        None => File::from_bencode(BEncode::Dict(data)).map(|f| vec![f]),
    }
}
<|endoftext|>


<commit_before>//! Decode Cryptographic Control (CC) packets.

use consts::CRYPTO_CONTROL_BYTES;
use util::slice_u16;

/// Buffer of bytes that represent a crypto control packet.
pub type Buf = [u8; CRYPTO_CONTROL_BYTES];

/// Information necessary to decrypt an encrypted message.
pub struct CryptoControlFields(Buf);

impl CryptoControlFields {
    /// Create a new `CryptoControlFields` decoder from the given bytes.
    pub fn new(buf: Buf) -> Self { CryptoControlFields(buf) }

    /// Initialization vector used internally by associated crypto algorithm.
    pub fn init(&self) -> &[u8] { &self.0[..9] }
    /// Type of crypto algorithm in use, if any.
    pub fn alg(&self) -> CryptoAlgorithm { CryptoAlgorithm::from_bits(self.0[9]) }
    /// Encryption key to use.
    pub fn key(&self) -> u16 { slice_u16(&self.0[10..]) }
}

/// Type of cryptographic algorithm.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CryptoAlgorithm {
    Accordion,
    BatonEven,
    Firefly,
    Mayfly,
    Saville,
    BatonOdd,
    Unencrypted,
    Des,
    TripleDes,
    Aes,
    Other(u8),
}

impl CryptoAlgorithm {
    /// Parse the given 8 bits into a crypto algorithm.
    pub fn from_bits(bits: u8) -> CryptoAlgorithm {
        use self::CryptoAlgorithm::*;

        match bits {
            0x00 => Accordion,
            0x01 => BatonEven,
            0x02 => Firefly,
            0x03 => Mayfly,
            0x04 => Saville,
            0x41 => BatonOdd,
            0x80 => Unencrypted,
            0x81 => Des,
            0x83 => TripleDes,
            0x84 => Aes,
            b => Other(b),
        }
    }
}

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

    #[test]
    fn test_cypto() {
        let c = CryptoControlFields::new([
            0, 0, 0, 1, 0, 0, 0, 2, 0,
            0b10000100,
            0xDE, 0xAD,
        ]);

        assert_eq!(c.init(), &[0,0,0,1,0,0,0,2,0]);
        assert_eq!(c.alg(), Aes);
        assert_eq!(c.key(), 0xDEAD);
    }
}
<commit_msg>Derive Serialize for CryptoAlgorithm<commit_after>//! Decode Cryptographic Control (CC) packets.

use consts::CRYPTO_CONTROL_BYTES;
use util::slice_u16;

/// Buffer of bytes that represent a crypto control packet.
pub type Buf = [u8; CRYPTO_CONTROL_BYTES];

/// Information necessary to decrypt an encrypted message.
pub struct CryptoControlFields(Buf);

impl CryptoControlFields {
    /// Create a new `CryptoControlFields` decoder from the given bytes.
    pub fn new(buf: Buf) -> Self { CryptoControlFields(buf) }

    /// Initialization vector used internally by associated crypto algorithm.
    pub fn init(&self) -> &[u8] { &self.0[..9] }
    /// Type of crypto algorithm in use, if any.
    pub fn alg(&self) -> CryptoAlgorithm { CryptoAlgorithm::from_bits(self.0[9]) }
    /// Encryption key to use.
    pub fn key(&self) -> u16 { slice_u16(&self.0[10..]) }
}

/// Type of cryptographic algorithm.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "ser", derive(Serialize))]
pub enum CryptoAlgorithm {
    Accordion,
    BatonEven,
    Firefly,
    Mayfly,
    Saville,
    BatonOdd,
    Unencrypted,
    Des,
    TripleDes,
    Aes,
    Other(u8),
}

impl CryptoAlgorithm {
    /// Parse the given 8 bits into a crypto algorithm.
    pub fn from_bits(bits: u8) -> CryptoAlgorithm {
        use self::CryptoAlgorithm::*;

        match bits {
            0x00 => Accordion,
            0x01 => BatonEven,
            0x02 => Firefly,
            0x03 => Mayfly,
            0x04 => Saville,
            0x41 => BatonOdd,
            0x80 => Unencrypted,
            0x81 => Des,
            0x83 => TripleDes,
            0x84 => Aes,
            b => Other(b),
        }
    }
}

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

    #[test]
    fn test_cypto() {
        let c = CryptoControlFields::new([
            0, 0, 0, 1, 0, 0, 0, 2, 0,
            0b10000100,
            0xDE, 0xAD,
        ]);

        assert_eq!(c.init(), &[0,0,0,1,0,0,0,2,0]);
        assert_eq!(c.alg(), Aes);
        assert_eq!(c.key(), 0xDEAD);
    }
}
<|endoftext|>


<commit_before>use std::io::{Read, Write};
use std::fmt;
use std::cell::{RefMut, RefCell};
use std::ascii::AsciiExt;

use serde::{Serialize, Deserialize};
use serde_json;
use curl;
use url;

use error::GGRResult;
use error::GGRError;

/// interface function for `handle_req`, set some fields if body has content
fn send_req<W: Write>(handle: &mut curl::easy::Easy,
                      out: &mut W,
                      body: Option<Vec<u8>>)
                      -> GGRResult<(u32, Vec<String>)> {
    match body {
        Some(body) => {
            let mut body = &body[..];
            handle.upload(true)?;
            handle.in_filesize(body.len() as u64)?;
            handle_req(handle, out, &mut |buf| body.read(buf).unwrap_or(0))
        }
        None => handle_req(handle, out, &mut |_| 0),
    }
}

/// do the curl request
fn handle_req<W: Write>(handle: &mut curl::easy::Easy,
                        out: &mut W,
                        read: &mut FnMut(&mut [u8]) -> usize)
                        -> GGRResult<(u32, Vec<String>)> {
    let mut headers = Vec::new();
    {
        let mut handle = handle.transfer();
        handle.read_function(|buf| Ok(read(buf)))?;
        handle.write_function(|data| {
                Ok(match out.write_all(data) {
                    Ok(_) => data.len(),
                    Err(_) => 0,
                })
            })?;
        handle.header_function(|data| {
                headers.push(String::from_utf8_lossy(data).into_owned());
                true
            })?;
        handle.perform()?;
    }

    Ok((handle.response_code()?, headers))
}

/// https actions
#[derive(PartialEq, Debug)]
enum CallMethod {
    Get,
    Post,
    Put,
    Delete,
}

impl fmt::Display for CallMethod {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CallMethod::Get => write!(f, "GET"),
            CallMethod::Post => write!(f, "POST"),
            CallMethod::Put => write!(f, "PUT"),
            CallMethod::Delete => write!(f, "DELETE"),
        }
    }
}

/// Interface to talk with a http server
pub struct Call {
    shared_handle: RefCell<curl::easy::Easy>,
    base: url::Url,
}

impl Call {
    /// create a new call object with url as endpoint
    pub fn new(url: &url::Url) -> Call {
        Call {
            shared_handle: RefCell::new(curl::easy::Easy::new()),
            base: url.clone(),
        }
    }

    /// change url objects query information
    pub fn set_url_query(&mut self, q: Option<&str>) {
        self.base.set_query(q);
    }

    // Low Level Methods

    /// set cookie and netrc options and returnes a CallRequest
    fn do_request(&self, method: &CallMethod, url: &str) -> GGRResult<CallRequest> {
        let mut handle = self.shared_handle.borrow_mut();
        try!(handle.cookie_session(true));
        try!(handle.netrc(curl::easy::NetRc::Required));

        CallRequest::new(handle, method, url)
    }

    /// call the do_request function two times. One with digest and the other with basic http
    /// authentication methods. The first success returnes a CallResponse
    fn request<S: Serialize>(&self, method: CallMethod, path: &str, body: Option<&S>) -> GGRResult<CallResponse> {
        let mut sendurl = self.base.clone();
        // double replace for pathes with three ///.
        let complete_path = format!("{}/{}", sendurl.path(), path).replace("//", "/").replace("//", "/");
        sendurl.set_path(&complete_path);

        debug!("url-to-send: {:?}", sendurl);

        for am in vec!(
            curl::easy::Auth::new().digest(true),
            curl::easy::Auth::new().basic(true),
        ) {
            let mut call_request = try!(self.do_request(&method, &sendurl.to_owned().into_string()));
            if let Some(body) = body {
                call_request.with_json_body(&body).ok();
            }

            try!(call_request.handle.http_auth(am));
            let call_response = call_request.send()?;

            if call_response.status() == 401 /* Unauthorized */ {
                debug!("status 401 ... try other http method if available");
                continue;
            }
            return Ok(call_response);
        }

        Err(GGRError::General("No Authentication algorithm found for your gerrit server. 'basic' and 'digest' tested".into()))
    }

    /// Convenience method that performs a `GET` request.
    pub fn get(&self, path: &str) -> GGRResult<CallResponse> {
        self.request::<String>(CallMethod::Get, path, None)
    }

    /// Convenience method that performs a `DELETE` request.
    pub fn delete(&self, path: &str) -> GGRResult<CallResponse> {
        self.request::<String>(CallMethod::Delete, path, None)
    }

    /// Convenience method that performs a `POST` request with JSON data.
    pub fn post<S: Serialize>(&self, path: &str, body: &S) -> GGRResult<CallResponse> {
        self.request(CallMethod::Post, path, Some(body))
    }

    /// Convenience method that performs a `PUT` request with JSON data.
    pub fn put<S: Serialize>(&self, path: &str, body: &S) -> GGRResult<CallResponse> {
        self.request(CallMethod::Put, path, Some(body))
    }
}

/// Iterator over response headers
#[allow(dead_code)]
pub struct Headers<'a> {
    lines: &'a [String],
    idx: usize,
}

impl<'a> Iterator for Headers<'a> {
    type Item = (&'a str, &'a str);

    fn next(&mut self) -> Option<(&'a str, &'a str)> {
        self.lines.get(self.idx).map(|line| {
            self.idx += 1;
            match line.find(':') {
                Some(i) => (&line[..i], line[i + 1..].trim()),
                None => (line[..].trim(), ""),
            }
        })
    }
}

/// present a http request
pub struct CallRequest<'a> {
    handle: RefMut<'a, curl::easy::Easy>,
    headers: curl::easy::List,
    body: Option<Vec<u8>>,
}

impl<'a> CallRequest<'a> {
    /// create a call request
    fn new(mut handle: RefMut<'a, curl::easy::Easy>,
           method: &CallMethod,
           url: &str)
           -> GGRResult<CallRequest<'a>> {
        debug!("request {} {}", method, url);

        let mut headers = curl::easy::List::new();
        headers.append("Accept: application/json").ok();

        match *method {
            CallMethod::Get => try!(handle.get(true)),
            CallMethod::Post => try!(handle.custom_request("POST")),
            CallMethod::Put => try!(handle.custom_request("PUT")),
            CallMethod::Delete => try!(handle.custom_request("DELETE")),
        }

        handle.url(url)?;

        Ok(CallRequest {
            handle: handle,
            headers: headers,
            body: None,
        })
    }

    /// adds a specific header to the request
    pub fn with_header(mut self, key: &str, value: &str) -> GGRResult<CallRequest<'a>> {
        self.headers.append(&format!("{}: {}", key, value))?;
        Ok(self)
    }

    /// sets the JSON request body for the request.
    pub fn with_json_body<S: Serialize>(&mut self, body: &S) -> GGRResult<&mut CallRequest<'a>> {
        let mut body_bytes: Vec<u8> = vec![];
        serde_json::to_writer(&mut body_bytes, &body)?;
        debug!("sending JSON data ({} bytes)", body_bytes.len());
        self.body = Some(body_bytes);
        self.headers.append("Content-Type: application/json")?;
        Ok(self)
    }

    /// attaches some form data to the request.
    pub fn with_form_data(&mut self, form: curl::easy::Form) -> GGRResult<&mut CallRequest<'a>> {
        debug!("sending form data");
        self.handle.httppost(form)?;
        self.body = None;
        Ok(self)
    }

    /// enables or disables redirects.  The default is off.
    pub fn follow_location(&mut self, val: bool) -> GGRResult<&mut CallRequest<'a>> {
        debug!("follow redirects: {}", val);
        self.handle.follow_location(val)?;
        Ok(self)
    }

    /// Sends the request and writes response data into the given file
    /// instead of the response object's in memory buffer.
    pub fn send_into<W: Write>(mut self, out: &mut W) -> GGRResult<CallResponse> {
        self.handle.http_headers(self.headers)?;
        let local_body = self.body.clone();
        let (status, headers) = send_req(&mut self.handle, out, local_body)?;
        debug!("response: {}", status);
        Ok(CallResponse {
            status: status,
            headers: headers,
            body: None,
        })
    }

    /// Sends the request and reads the response body into the response object.
    pub fn send(self) -> GGRResult<CallResponse> {
        let mut out = vec![];
        let mut rv = self.send_into(&mut out)?;

        debug!("return-from-server: {:?}", rv);

        // cut first 4 bytes from output stream
        // **NOTICE**: The first 4 characters are cutted from the returned content. We want only
        // json data which has a prevention against XSSI attacks. More here:
        // <https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/rest-api.html#output>
        if out.starts_with(b")]}'") {
            out = out[4..].into();
        }

        rv.body = Some(out);
        Ok(rv)
    }
}

/// represent a http resonse
#[derive(Clone, Debug)]
pub struct CallResponse {
    status: u32,
    headers: Vec<String>,
    body: Option<Vec<u8>>,
}

impl CallResponse {
    /// Returns the status code of the response
    pub fn status(&self) -> u32 {
        self.status
    }

    /// Indicates that the request failed
    pub fn failed(&self) -> bool {
        self.status >= 400 && self.status <= 600
    }

    /// Indicates that the request succeeded
    pub fn ok(&self) -> bool {
        !self.failed()
    }

    /// Converts the response into a result object.  This also converts non okay response codes
    /// into errors.
    pub fn to_result(&self) -> GGRResult<&CallResponse> {
        debug!("headers:");
        for (header_key, header_value) in self.headers() {
            if !header_value.is_empty() {
                debug!("  {}: {}", header_key, header_value);
            }
        }
        if let Some(ref body) = self.body {
            debug!("body: {}", String::from_utf8_lossy(body));
        }
        if self.ok() {
            return Ok(self);
        }
        Err(GGRError::General(format!("generic error: {}", self.status())))
    }

    /// Deserializes the response body into the given type
    pub fn deserialize<T: Deserialize>(&self) -> GGRResult<T> {
        Ok(serde_json::from_reader(match self.body {
            Some(ref body) => body,
            None => &b""[..],
        })?)
    }

    /// Like `deserialize` but consumes the response and will convert
    /// failed requests into proper errors.
    pub fn convert<T: Deserialize>(self) -> GGRResult<T> {
        self.to_result().and_then(|x| x.deserialize())
    }

    /// Iterates over the headers.
    #[allow(dead_code)]
    pub fn headers(&self) -> Headers {
        Headers {
            lines: &self.headers[..],
            idx: 0,
        }
    }

    /// Looks up the first matching header for a key.
    #[allow(dead_code)]
    pub fn get_header(&self, key: &str) -> Option<&str> {
        for (header_key, header_value) in self.headers() {
            if header_key.eq_ignore_ascii_case(key) {
                return Some(header_value);
            }
        }
        None
    }

    /// give back the body content
    pub fn get_body(&self) -> Option<Vec<u8>> {
        self.body.clone()
    }
}
<commit_msg>leak base url to world in libgerrit/call<commit_after>use std::io::{Read, Write};
use std::fmt;
use std::cell::{RefMut, RefCell};
use std::ascii::AsciiExt;

use serde::{Serialize, Deserialize};
use serde_json;
use curl;
use url;

use error::GGRResult;
use error::GGRError;

/// interface function for `handle_req`, set some fields if body has content
fn send_req<W: Write>(handle: &mut curl::easy::Easy,
                      out: &mut W,
                      body: Option<Vec<u8>>)
                      -> GGRResult<(u32, Vec<String>)> {
    match body {
        Some(body) => {
            let mut body = &body[..];
            handle.upload(true)?;
            handle.in_filesize(body.len() as u64)?;
            handle_req(handle, out, &mut |buf| body.read(buf).unwrap_or(0))
        }
        None => handle_req(handle, out, &mut |_| 0),
    }
}

/// do the curl request
fn handle_req<W: Write>(handle: &mut curl::easy::Easy,
                        out: &mut W,
                        read: &mut FnMut(&mut [u8]) -> usize)
                        -> GGRResult<(u32, Vec<String>)> {
    let mut headers = Vec::new();
    {
        let mut handle = handle.transfer();
        handle.read_function(|buf| Ok(read(buf)))?;
        handle.write_function(|data| {
                Ok(match out.write_all(data) {
                    Ok(_) => data.len(),
                    Err(_) => 0,
                })
            })?;
        handle.header_function(|data| {
                headers.push(String::from_utf8_lossy(data).into_owned());
                true
            })?;
        handle.perform()?;
    }

    Ok((handle.response_code()?, headers))
}

/// https actions
#[derive(PartialEq, Debug)]
enum CallMethod {
    Get,
    Post,
    Put,
    Delete,
}

impl fmt::Display for CallMethod {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CallMethod::Get => write!(f, "GET"),
            CallMethod::Post => write!(f, "POST"),
            CallMethod::Put => write!(f, "PUT"),
            CallMethod::Delete => write!(f, "DELETE"),
        }
    }
}

/// Interface to talk with a http server
pub struct Call {
    shared_handle: RefCell<curl::easy::Easy>,
    base: url::Url,
}

impl Call {
    /// create a new call object with url as endpoint
    pub fn new(url: &url::Url) -> Call {
        Call {
            shared_handle: RefCell::new(curl::easy::Easy::new()),
            base: url.clone(),
        }
    }

    /// change url objects query information
    pub fn set_url_query(&mut self, q: Option<&str>) {
        self.base.set_query(q);
    }

    /// return reference t base url object
    pub fn get_base(&self) -> &url::Url {
        &self.base
    }

    // Low Level Methods

    /// set cookie and netrc options and returnes a CallRequest
    fn do_request(&self, method: &CallMethod, url: &str) -> GGRResult<CallRequest> {
        let mut handle = self.shared_handle.borrow_mut();
        try!(handle.cookie_session(true));
        try!(handle.netrc(curl::easy::NetRc::Required));

        CallRequest::new(handle, method, url)
    }

    /// call the do_request function two times. One with digest and the other with basic http
    /// authentication methods. The first success returnes a CallResponse
    fn request<S: Serialize>(&self, method: CallMethod, path: &str, body: Option<&S>) -> GGRResult<CallResponse> {
        let mut sendurl = self.base.clone();
        // double replace for pathes with three ///.
        let complete_path = format!("{}/{}", sendurl.path(), path).replace("//", "/").replace("//", "/");
        sendurl.set_path(&complete_path);

        debug!("url-to-send: {:?}", sendurl);

        for am in vec!(
            curl::easy::Auth::new().digest(true),
            curl::easy::Auth::new().basic(true),
        ) {
            let mut call_request = try!(self.do_request(&method, &sendurl.to_owned().into_string()));
            if let Some(body) = body {
                call_request.with_json_body(&body).ok();
            }

            try!(call_request.handle.http_auth(am));
            let call_response = call_request.send()?;

            if call_response.status() == 401 /* Unauthorized */ {
                debug!("status 401 ... try other http method if available");
                continue;
            }
            return Ok(call_response);
        }

        Err(GGRError::General("No Authentication algorithm found for your gerrit server. 'basic' and 'digest' tested".into()))
    }

    /// Convenience method that performs a `GET` request.
    pub fn get(&self, path: &str) -> GGRResult<CallResponse> {
        self.request::<String>(CallMethod::Get, path, None)
    }

    /// Convenience method that performs a `DELETE` request.
    pub fn delete(&self, path: &str) -> GGRResult<CallResponse> {
        self.request::<String>(CallMethod::Delete, path, None)
    }

    /// Convenience method that performs a `POST` request with JSON data.
    pub fn post<S: Serialize>(&self, path: &str, body: &S) -> GGRResult<CallResponse> {
        self.request(CallMethod::Post, path, Some(body))
    }

    /// Convenience method that performs a `PUT` request with JSON data.
    pub fn put<S: Serialize>(&self, path: &str, body: &S) -> GGRResult<CallResponse> {
        self.request(CallMethod::Put, path, Some(body))
    }
}

/// Iterator over response headers
#[allow(dead_code)]
pub struct Headers<'a> {
    lines: &'a [String],
    idx: usize,
}

impl<'a> Iterator for Headers<'a> {
    type Item = (&'a str, &'a str);

    fn next(&mut self) -> Option<(&'a str, &'a str)> {
        self.lines.get(self.idx).map(|line| {
            self.idx += 1;
            match line.find(':') {
                Some(i) => (&line[..i], line[i + 1..].trim()),
                None => (line[..].trim(), ""),
            }
        })
    }
}

/// present a http request
pub struct CallRequest<'a> {
    handle: RefMut<'a, curl::easy::Easy>,
    headers: curl::easy::List,
    body: Option<Vec<u8>>,
}

impl<'a> CallRequest<'a> {
    /// create a call request
    fn new(mut handle: RefMut<'a, curl::easy::Easy>,
           method: &CallMethod,
           url: &str)
           -> GGRResult<CallRequest<'a>> {
        debug!("request {} {}", method, url);

        let mut headers = curl::easy::List::new();
        headers.append("Accept: application/json").ok();

        match *method {
            CallMethod::Get => try!(handle.get(true)),
            CallMethod::Post => try!(handle.custom_request("POST")),
            CallMethod::Put => try!(handle.custom_request("PUT")),
            CallMethod::Delete => try!(handle.custom_request("DELETE")),
        }

        handle.url(url)?;

        Ok(CallRequest {
            handle: handle,
            headers: headers,
            body: None,
        })
    }

    /// adds a specific header to the request
    pub fn with_header(mut self, key: &str, value: &str) -> GGRResult<CallRequest<'a>> {
        self.headers.append(&format!("{}: {}", key, value))?;
        Ok(self)
    }

    /// sets the JSON request body for the request.
    pub fn with_json_body<S: Serialize>(&mut self, body: &S) -> GGRResult<&mut CallRequest<'a>> {
        let mut body_bytes: Vec<u8> = vec![];
        serde_json::to_writer(&mut body_bytes, &body)?;
        debug!("sending JSON data ({} bytes)", body_bytes.len());
        self.body = Some(body_bytes);
        self.headers.append("Content-Type: application/json")?;
        Ok(self)
    }

    /// attaches some form data to the request.
    pub fn with_form_data(&mut self, form: curl::easy::Form) -> GGRResult<&mut CallRequest<'a>> {
        debug!("sending form data");
        self.handle.httppost(form)?;
        self.body = None;
        Ok(self)
    }

    /// enables or disables redirects.  The default is off.
    pub fn follow_location(&mut self, val: bool) -> GGRResult<&mut CallRequest<'a>> {
        debug!("follow redirects: {}", val);
        self.handle.follow_location(val)?;
        Ok(self)
    }

    /// Sends the request and writes response data into the given file
    /// instead of the response object's in memory buffer.
    pub fn send_into<W: Write>(mut self, out: &mut W) -> GGRResult<CallResponse> {
        self.handle.http_headers(self.headers)?;
        let local_body = self.body.clone();
        let (status, headers) = send_req(&mut self.handle, out, local_body)?;
        debug!("response: {}", status);
        Ok(CallResponse {
            status: status,
            headers: headers,
            body: None,
        })
    }

    /// Sends the request and reads the response body into the response object.
    pub fn send(self) -> GGRResult<CallResponse> {
        let mut out = vec![];
        let mut rv = self.send_into(&mut out)?;

        debug!("return-from-server: {:?}", rv);

        // cut first 4 bytes from output stream
        // **NOTICE**: The first 4 characters are cutted from the returned content. We want only
        // json data which has a prevention against XSSI attacks. More here:
        // <https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/rest-api.html#output>
        if out.starts_with(b")]}'") {
            out = out[4..].into();
        }

        rv.body = Some(out);
        Ok(rv)
    }
}

/// represent a http resonse
#[derive(Clone, Debug)]
pub struct CallResponse {
    status: u32,
    headers: Vec<String>,
    body: Option<Vec<u8>>,
}

impl CallResponse {
    /// Returns the status code of the response
    pub fn status(&self) -> u32 {
        self.status
    }

    /// Indicates that the request failed
    pub fn failed(&self) -> bool {
        self.status >= 400 && self.status <= 600
    }

    /// Indicates that the request succeeded
    pub fn ok(&self) -> bool {
        !self.failed()
    }

    /// Converts the response into a result object.  This also converts non okay response codes
    /// into errors.
    pub fn to_result(&self) -> GGRResult<&CallResponse> {
        debug!("headers:");
        for (header_key, header_value) in self.headers() {
            if !header_value.is_empty() {
                debug!("  {}: {}", header_key, header_value);
            }
        }
        if let Some(ref body) = self.body {
            debug!("body: {}", String::from_utf8_lossy(body));
        }
        if self.ok() {
            return Ok(self);
        }
        Err(GGRError::General(format!("generic error: {}", self.status())))
    }

    /// Deserializes the response body into the given type
    pub fn deserialize<T: Deserialize>(&self) -> GGRResult<T> {
        Ok(serde_json::from_reader(match self.body {
            Some(ref body) => body,
            None => &b""[..],
        })?)
    }

    /// Like `deserialize` but consumes the response and will convert
    /// failed requests into proper errors.
    pub fn convert<T: Deserialize>(self) -> GGRResult<T> {
        self.to_result().and_then(|x| x.deserialize())
    }

    /// Iterates over the headers.
    #[allow(dead_code)]
    pub fn headers(&self) -> Headers {
        Headers {
            lines: &self.headers[..],
            idx: 0,
        }
    }

    /// Looks up the first matching header for a key.
    #[allow(dead_code)]
    pub fn get_header(&self, key: &str) -> Option<&str> {
        for (header_key, header_value) in self.headers() {
            if header_key.eq_ignore_ascii_case(key) {
                return Some(header_value);
            }
        }
        None
    }

    /// give back the body content
    pub fn get_body(&self) -> Option<Vec<u8>> {
        self.body.clone()
    }
}
<|endoftext|>