text:
use std::env;
use std::io::{Read, Write};
use std::fs::OpenOptions;
use std::collections::btree_map::BTreeMap;
use base64;
use hex;
//use fuse;
use interactor::*;
use secstr::SecStr;
use freepass_core::output::*;
use freepass_core::data::*;
use freepass_core::util::{guess_usage_stored, guess_usage_derived};
use freepass_core::vault::{Vault, WritableVault};
use freepass_core::encvault::*;
use openfile::*;
use util;

macro_rules! interaction {
    ( { $($action_name:expr => $action_fn:expr),+ }, $data:expr, $data_fn:expr ) => {
        {
            let mut items = vec![$(">> ".to_owned() + $action_name),+];
            let data_items : Vec<String> = $data.map(|x| " | ".to_owned() + x).collect();
            items.extend(data_items.iter().cloned());
            match pick_from_list(util::menu_cmd().as_mut(), &items[..], "Selection: ").unwrap() {
                $(ref x if *x == ">> ".to_owned() + $action_name => $action_fn),+
                ref x if data_items.contains(x) => ($data_fn)(&x[3..]),
                ref x => panic!("Unknown selection: {}", x),
            }
        }
    };
    ( { $($action_name:expr => $action_fn:expr),+ }) => {
        {
            let items = vec![$(">> ".to_owned() + $action_name),+];
            match pick_from_list(util::menu_cmd().as_mut(), &items[..], "Selection: ").unwrap() {
                $(ref x if *x == ">> ".to_owned() + $action_name => $action_fn),+
                ref x => panic!("Unknown selection: {}", x),
            }
        }
    }
}

pub fn interact_entries(open_file: &mut OpenFile, debug: bool) {
    loop {
        interaction!({
            "Quit" => {
                return ();
            },
            "Add new entry" => {
                if let Some(entry_name) = util::read_text("Entry name") {
                    interact_entry_edit(open_file, &entry_name, Entry::default(), EntryMetadata::default());
                }
            }
        }, open_file.vault.entry_names(), |name| {
            let (entry, meta) = open_file.vault.get_entry(name).expect("Couldn't read selected entry");
            if debug {
                util::debug_output(&entry, &format!("Entry: {}", name));
            }
            interact_entry(open_file, name, entry, meta);
        });
    }
}

fn interact_entry(open_file: &mut OpenFile, entry_name: &str, entry: Entry, meta: EntryMetadata) {
    loop {
        interaction!({
            "Go back" => {
                return ();
            },
            &format!("Name:          {}", entry_name) => {},
            &format!("Last modified: {}", meta.updated_at.to_rfc2822()) => {},
            &format!("Created:       {}", meta.created_at.to_rfc2822()) => {},
            "Edit" => {
                return interact_entry_edit(open_file, entry_name, entry, meta);
            }
        }, entry.fields.keys(), |name: &str| {
            let field = entry.fields.get(name).expect("Couldn't read selected field");
            let output = process_output(entry_name, &open_file.master_key, field).unwrap();
            match output {
                Output::PrivateText(s) => println!("{}", String::from_utf8(Vec::from(s.unsecure())).expect("Couldn't decode UTF-8")),
                Output::OpenText(s) => println!("{}", s),
                Output::PrivateBinary(s) => {
                    interaction!({
                        "Go back" => {},
                        "Print as Base64" => { println!("{}", base64::encode(s.unsecure())) },
                        "Print as hex" => { println!("{}", hex::encode(s.unsecure())) }
                    })
                },
                Output::Ed25519Keypair(usage, _, _) => match usage {
                    Ed25519Usage::SSH => {
                        interaction!({
                            "Go back" => {},
                            "Print public key" => { println!("{}", ssh_public_key_output(&output, entry_name).unwrap()) },
                            "Add private key to ssh-agent" => {
                                ssh_agent_send_message(ssh_private_key_agent_message(&output, entry_name).unwrap()).unwrap()
                            }
                        })
                    },
                    Ed25519Usage::Signify => {
                        interaction!({
                            "Go back" => {},
                            "Print public key" => { println!("{}", signify_public_key_output(&output, entry_name).unwrap()) },
                            "Sign a file" => {
                                let path = pick_file(util::menu_cmd, env::current_dir().unwrap()).unwrap();
                                let mut sigfile = OpenOptions::new().write(true).create(true).open(format!("{}.sig", path.to_str().unwrap())).unwrap();
                                let mut buffer = Vec::new();
                                OpenOptions::new().read(true).open(path).unwrap().read_to_end(&mut buffer).unwrap();
                                let signature = signify_sign(&output, &format!("signed with freepass key: {}", entry_name), &buffer[..]).unwrap().into_bytes();
                                sigfile.write_all(&signature[..]).expect("Couldn't write the signature to the file");
                            }
                        })
                    },
                    _ => panic!("Unsupported key usage"),
                },
                Output::Attachments(attachments) => {
                    interaction!({
                        "Go back" => {},
                        "Mount attachments folder" => {
                            println!("[TODO] Mounting attachments folder '{}'. Unmount to continue.", entry_name);
                            //fuse::mount(attachments, &"/home/greg/fp", &[]);
                        }
                    })
                }
            }
        });
    }
}

fn interact_entry_edit(open_file: &mut OpenFile, entry_name: &str, mut entry: Entry, mut meta: EntryMetadata) {
    interaction!({
        &format!("  Save entry [{}]", entry_name) => {
            open_file.vault.put_entry(entry_name, &entry, &mut meta).unwrap();
            open_file.save();
            return interact_entry(open_file, entry_name, entry, meta);
        },
        &format!("Delete entry [{}]", entry_name) => {
            interaction!({
                "Cancel" => {
                    return interact_entry_edit(open_file, entry_name, entry, meta);
                },
                &format!("DELETE THE ENTRY '{}'!", entry_name) => {
                    open_file.vault.remove_entry(entry_name);
                    open_file.save();
                    return ();
                }
            })
        },
        &format!("Rename entry [{}]", entry_name) => {
            let new_entry_name = util::read_text(&format!("New entry name [{}]", entry_name)).unwrap_or(entry_name.to_owned());
            open_file.vault.remove_entry(entry_name);
            open_file.vault.put_entry(&new_entry_name, &entry, &mut meta).unwrap();
            return interact_entry_edit(open_file, &new_entry_name, entry, meta);
        },
        "Add field" => {
            if let Some(field_name) = util::read_text("Field name") {
                entry = interact_field_edit(&mut open_file.vault, entry, field_name);
            }
            return interact_entry_edit(open_file, entry_name, entry, meta);
        }
    }, entry.fields.keys(), |name: &str| {
        entry = interact_field_edit(&mut open_file.vault, entry, name.to_owned());
        return interact_entry_edit(open_file, entry_name, entry, meta);
    });
}

fn new_derived_field(field_name: &str) -> Field {
    Field::Derived {
        counter: 1,
        site_name: None,
        usage: guess_usage_derived(field_name),
    }
}

fn new_stored_field(field_name: &str) -> Field {
    Field::Stored {
        data: SecStr::new(Vec::new()),
        usage: guess_usage_stored(field_name),
    }
}

fn new_field(field_name: &str) -> Field {
    let fname = field_name.to_lowercase();
    if fname.contains("name") || fname.contains("login") || fname.contains("mail") || fname.contains("pin") {
        new_stored_field(field_name)
    } else {
        new_derived_field(field_name)
    }
}

fn interact_field_edit(vault: &mut DecryptedVault, mut entry: Entry, field_name: String) -> Entry {
    let mut field = entry.fields.remove(&field_name).unwrap_or_else(
        || new_field(&field_name),
    );
    let mut field_actions: BTreeMap<String, Box<Fn(Field) -> Field>> = BTreeMap::new();
    let other_type;
    match field.clone() {
        Field::Derived { counter, site_name, usage } => {
            other_type = "stored";
            field_actions.insert(
                format!("Counter: {}", counter),
                Box::new(|f| if let Field::Derived { counter, site_name, usage } = f {
                    let new_counter = util::read_text(&format!("Counter [{}]", counter))
                        .and_then(|c| c.parse::<u32>().ok())
                        .unwrap_or(counter);
                    Field::Derived { counter: new_counter, site_name: site_name, usage: usage }
                } else {
                    unreachable!();
                }),
            );
            field_actions.insert(
                match site_name {
                    Some(ref sn) => format!("Site name: {}", sn),
                    None => format!("Site name: <same as entry name>"),
                },
                Box::new(|f| if let Field::Derived { counter, usage, .. } = f {
                    let new_site_name = util::read_text("Site name");
                    Field::Derived { counter: counter, site_name: new_site_name, usage: usage }
                } else {
                    unreachable!();
                }),
            );
            field_actions.insert(
                format!("Usage: {:?}", usage),
                Box::new(|f| if let Field::Derived { counter, site_name, .. } = f {
                    let new_usage = interaction!({
                        "Password(Maximum)"   => { DerivedUsage::Password(PasswordTemplate::Maximum) },
                        "Password(Long)"      => { DerivedUsage::Password(PasswordTemplate::Long) },
                        "Password(Medium)"    => { DerivedUsage::Password(PasswordTemplate::Medium) },
                        "Password(Short)"     => { DerivedUsage::Password(PasswordTemplate::Short) },
                        "Password(Basic)"     => { DerivedUsage::Password(PasswordTemplate::Basic) },
                        "Password(Pin)"       => { DerivedUsage::Password(PasswordTemplate::Pin) },
                        "Ed25519Key(SSH)"     => { DerivedUsage::Ed25519Key(Ed25519Usage::SSH) },
                        "Ed25519Key(Signify)" => { DerivedUsage::Ed25519Key(Ed25519Usage::Signify) },
                        "Ed25519Key(SQRL)"    => { DerivedUsage::Ed25519Key(Ed25519Usage::SQRL) },
                        "RawKey"              => { DerivedUsage::RawKey }
                    });
                    Field::Derived { counter: counter, site_name: site_name, usage: new_usage }
                } else {
                    unreachable!();
                }),
            );
        },
        Field::Stored { usage, data, .. } => {
            other_type = "derived";
            let txt = String::from_utf8(data.unsecure().to_vec()).unwrap_or("<invalid UTF-8>".to_owned());
            field_actions.insert(
                format!("Change text [{}]", txt),
                Box::new(|f| if let Field::Stored { usage, data, .. } = f {
                    let txt = String::from_utf8(data.unsecure().to_vec()).unwrap_or("<invalid UTF-8>".to_owned());
                    let new_data = util::read_text(&format!("New text [{}]", txt)).unwrap_or(txt);
                    Field::Stored { data: SecStr::from(new_data), usage: usage }
                } else {
                    unreachable!();
                }),
            );
            field_actions.insert(
                format!("Usage: {:?}", usage),
                Box::new(|f| if let Field::Stored { data, .. } = f {
                    let new_usage = interaction!({
                        "Password"            => { StoredUsage::Password },
                        "Text"                => { StoredUsage::Text },
                        "Attachments"         => { StoredUsage::Attachments }
                    });
                    Field::Stored { data: data, usage: new_usage }
                } else {
                    unreachable!();
                }),
            );
        },
    };
    interaction!({
        "Go back" => {
            entry.fields.insert(field_name, field);
            return entry;
        },
        &format!("Delete field [{}]", field_name) => {
            interaction!({
                "Cancel" => {
                    return interact_field_edit(vault, entry, field_name);
                },
                &format!("DELETE THE FIELD '{}'!", field_name) => {
                    entry.fields.remove(&field_name);
                    return entry;
                }
            })
        },
        &format!("Rename field [{}]", field_name) => {
            let new_field_name = util::read_text(&format!("New field name [{}]", field_name)).unwrap_or(field_name.to_owned());
            entry.fields.insert(new_field_name.clone(), field);
            return interact_field_edit(vault, entry, new_field_name);
        },
        &format!("Change type to {}", other_type) => {
            entry.fields.remove(&field_name);
            entry.fields.insert(field_name.clone(), match field {
                Field::Derived { .. } => new_stored_field(&field_name),
                Field::Stored { .. } => new_derived_field(&field_name),
            });
            return interact_field_edit(vault, entry, field_name);
        }
    }, field_actions.keys(), |key| {
        field = field_actions.get(key).unwrap()(field);
        entry.fields.insert(field_name.clone(), field);
        return interact_field_edit(vault, entry, field_name);
    })
}


text:
use freepass_core::merge::*;
use freepass_core::vault::{Vault, WritableVault};
use util;

pub fn merge_in<I: ?Sized, F: ?Sized>(into_vault: &mut I, from_vault: &F)
    where I: Vault + WritableVault,
          F: Vault
{
    let log = merge_vaults(into_vault, from_vault);
    for lentry in &log {
        match *lentry {
            MergeLogEntry::Added(ref entry_name) => println!("Added: {}", entry_name),
            MergeLogEntry::IsNewer(_) => (),
            MergeLogEntry::IsOlder(ref entry_name) => println!("Not updated in the second file: {}", entry_name),
            MergeLogEntry::WeirdError(ref entry_name) => println!("ERROR! Couldn't add: {}", entry_name),
        }
    }
    // Handling all IsNewers together for better output
    for lentry in &log {
        if let MergeLogEntry::IsNewer(ref entry_name) = *lentry {
            if util::read_yesno(&format!("Update entry '{}'?", entry_name)) {
                if let Ok((from_entry, from_entry_meta)) = from_vault.get_entry(&entry_name) {
                    if let Ok(_) = into_vault.put_entry(&entry_name, &from_entry, &mut from_entry_meta.clone()) {
                        println!("Added: {}", entry_name)
                    } else {
                        println!("ERROR! Couldn't add: {}", entry_name)
                    }
                } else {
                    println!("ERROR! Couldn't add: {}", entry_name)
                }
            }
        }
    }
}

repo_name: valpackett/freepass

repo_language: Rust

mime_type: text/plain


text:
use std::{env, io, fs};
use std::io::prelude::*;
use std::process::{Command, Stdio};
use serde_cbor;
use serde::Serialize;
use hex;
use colorhash256;
use interactor;
use secstr::SecStr;
use ansi_term::Colour::Fixed;
use ansi_term::ANSIStrings;

pub fn menu_cmd() -> Option<Command> {
    env::var_os("FREEPASS_MENU").or(env::var_os("MENU")).map(
        |s| {
            let mut cmd = Command::new(s);
            cmd.env("FREEPASS_MODE", "MENU");
            cmd
        },
    )
}

pub fn read_password_console() -> SecStr {
    SecStr::new(
        interactor::read_from_tty(
            |buf, b, tty| {
                if b == 4 {
                    tty.write(b"\r                \r").unwrap();
                    return;
                }
                let color_string = if buf.len() < 8 {
                    // Make it a bit harder to recover the password by e.g. someone filming how you're entering your password
                    // Although if you're entering your password on camera, you're kinda screwed anyway
                    b"\rPassword: ~~~~~~".to_vec()
                } else {
                    let colors = colorhash256::hash_as_ansi(buf);
                    format!("\rPassword: {}",
                ANSIStrings(&[
                    Fixed(colors[0] as u8).paint("~~"),
                    Fixed(colors[1] as u8).paint("~~"),
                    Fixed(colors[2] as u8).paint("~~"),
                ]))
                        .into_bytes()
                };
                tty.write(&color_string).unwrap();
            },
            true,
            true,
        ).unwrap(),
    )
}

pub fn read_password_askpass(mut command: Command) -> SecStr {
    let process = command.stdout(Stdio::piped()).spawn().expect(
        "Couldn't spawn askpass program",
    );
    let mut result = Vec::new();
    let mut reader = io::BufReader::new(process.stdout.unwrap());
    let size = reader.read_until(b'\n', &mut result).unwrap();
    result.truncate(size - 1);
    SecStr::new(result)
}

pub fn read_password() -> SecStr {
    match env::var_os("FREEPASS_ASKPASS").or(env::var_os("ASKPASS")) {
        Some(s) => read_password_askpass(Command::new(s)),
        None => read_password_console(),
    }
}

pub fn read_text_console(prompt: &str) -> Option<String> {
    let mut tty = fs::OpenOptions::new()
        .read(true)
        .write(true)
        .open("/dev/tty")
        .expect("Couldn't open terminal device /dev/tty");
    tty.write(&format!("\r{}: ", prompt).into_bytes()).unwrap();
    let mut reader = io::BufReader::new(tty);
    let mut input = String::new();
    reader.read_line(&mut input).unwrap();
    input = input.replace("\n", "");
    if input.len() > 0 { Some(input) } else { None }
}

pub fn read_text_asktext(mut command: Command, prompt: &str) -> Option<String> {
    match command
              .env("FREEPASS_MODE", "TEXT")
              .env("FREEPASS_PROMPT", prompt)
              .output() {
        Ok(ref output) if output.status.success() => Some(String::from_utf8_lossy(&output.stdout).replace("\n", "")),
        _ => None,
    }
}

pub fn read_text(prompt: &str) -> Option<String> {
    match env::var_os("FREEPASS_ASKTEXT") {
        Some(s) => read_text_asktext(Command::new(s), prompt),
        None => read_text_console(prompt),
    }
}

pub fn read_yesno(prompt: &str) -> bool {
    loop {
        if let Some(x) = read_text(&format!("{} [y/n]", prompt)) {
            match x.to_lowercase().as_ref() {
                "y" | "yes" | "yeah" | "yep" => return true,
                "n" | "no" | "nope" => return false,
                _ => (),
            }
        }
    }
}

pub fn debug_output<T: Serialize>(data: &T, description: &str) {
    println!("--- CBOR debug output (http://cbor.me to decode) of {} ---\n{}\n", description,
        hex::encode(serde_cbor::to_vec(data).unwrap()));
}


text:
extern crate clap;
//extern crate fuse;
extern crate secstr;
extern crate colorhash256;
extern crate interactor;
extern crate rusterpassword;
extern crate ansi_term;
extern crate hex;
extern crate base64;
extern crate serde;
extern crate serde_cbor;
extern crate csv;
extern crate freepass_core;

mod util;
mod openfile;
mod interact;
mod mergein;

use std::{env, fs, io};
use clap::{Arg, App, SubCommand};
use openfile::*;
use freepass_core::{import, vault::{self, Vault}, output};

fn main() {
    let matches = App::new("freepass")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Greg V <greg@unrelenting.technology>")
        .about("The free password manager for power users")
        .arg(
            Arg::with_name("FILE")
                .short("f")
                .long("file")
                .takes_value(true)
                .help("The vault file to use, by default: $FREEPASS_FILE"),
        )
        .arg(
            Arg::with_name("NAME")
                .short("n")
                .long("name")
                .takes_value(true)
                .help("The user name to use (must be always the same for a vault file!), by default: $FREEPASS_NAME"),
        )
        .arg(
            Arg::with_name("DEBUG").long("debug").help(
                "Enable logging of data structures for debugging (DO NOT USE ON YOUR REAL DATA)",
            ),
        )
        .subcommand(SubCommand::with_name("interact").about("Launches interactive mode"))
        .subcommand(SubCommand::with_name("export").about("Exports all records as CSV (Bitwarden compatible fields)"))
        .subcommand(
            SubCommand::with_name("mergein")
                .about(
                    "Adds entires from a second file (possibly importing from a foreign format) that don't exist in the first file (e.g. to resolve file sync conflicts)",
                )
                .arg(
                    Arg::with_name("IMPORTTYPE")
                        .short("i")
                        .long("import")
                        .takes_value(true)
                        .help(
                            "If you want to import from a foreign file format instead of merging a second freepass vault, the format of that file. Supported: kdbx",
                        ),
                )
                .arg(
                    Arg::with_name("SECONDFILE")
                        .short("F")
                        .long("secondfile")
                        .takes_value(true)
                        .help("The vault file to get additional entries from, by default: $FREEPASS_SECOND_FILE"),
                )
                .arg(
                    Arg::with_name("SECONDNAME")
                        .short("N")
                        .long("secondname")
                        .takes_value(true)
                        .help("The user name to use for the second file, by default: $FREEPASS_SECOND_NAME or the first file name"),
                ),
        )
        .get_matches();

    let file_path = unwrap_for_opt(opt_or_env(&matches, "FILE", "FREEPASS_FILE"), "file");
    let user_name = unwrap_for_opt(opt_or_env(&matches, "NAME", "FREEPASS_NAME"), "name");
    let debug = matches.is_present("DEBUG");

    freepass_core::init();

    // Ensure we can write! Maybe someone somewhere would want to open the vault in read-only mode...
    // But the frustration of trying to save the vault while only having read permissions would be worse.
    let mut open_file = OpenFile::open(file_path.clone(), &user_name, util::read_password(), true);

    if debug {
        util::debug_output(&open_file.vault.data, "Vault");
    }

    match matches.subcommand() {

        ("mergein", submatches_opt) => {
            if let Some(submatches) = submatches_opt {
                let second_file_path = unwrap_for_opt(opt_or_env(submatches, "SECONDFILE", "FREEPASS_SECOND_FILE"), "secondfile");
                let second_vault: Box<vault::Vault> = match submatches.value_of("IMPORTTYPE") {
                    Some("kdbx") => {
                        let mut second_file = match fs::OpenOptions::new().read(true).open(&second_file_path) {
                            Ok(file) => file,
                            Err(ref err) => panic!("Could not open file {}: {}", &second_file_path, err),
                        };
                        Box::new(import::kdbx(&mut second_file, &util::read_password()).expect("Could not read the file as kdbx"))
                    },
                    Some(x) => panic!("Unsupported import format {}", x),
                    None => {
                        let second_user_name = opt_or_env(submatches, "SECONDNAME", "FREEPASS_SECOND_NAME").unwrap_or(user_name);
                        let second_open_file = OpenFile::open(second_file_path, &second_user_name, util::read_password(), false);
                        if debug {
                            util::debug_output(&second_open_file.vault.data, "Second Vault");
                        }
                        Box::new(second_open_file.vault)
                    },
                };
                mergein::merge_in(&mut open_file.vault, &*second_vault);
                open_file.save();
            } else {
                panic!("No options for mergein")
            }
        },

        ("export", _) => {
            let stdout = io::stdout();
            let mut stdout = stdout.lock();
            let mut writer = csv::Writer::from_writer(stdout);
            writer.write_record(&["folder", "favorite", "type", "name", "notes", "fields", "login_uri", "login_username", "login_password", "login_totp"]).unwrap();
            for name in open_file.vault.entry_names() {
                let (entry, _meta) = open_file.vault.get_entry(name).expect("Couldn't read selected entry");
                let username = match entry.fields.get("username").or(entry.fields.get("login")).and_then(|f| output::process_output(name, &open_file.master_key, f).ok()) {
                    Some(output::Output::OpenText(s)) => s,
                    _ => "".to_string(),
                };
                let password = match entry.fields.get("password").and_then(|f| output::process_output(name, &open_file.master_key, f).ok()) {
                    Some(output::Output::PrivateText(s)) => String::from_utf8(Vec::from(s.unsecure())).expect("Couldn't decode UTF-8"),
                    _ => "".to_string(),
                };
                let extras = entry.fields.iter().filter(|(k, _)| *k != "username" && *k != "login" && *k != "password").filter_map(|(k, v)| {
                    match output::process_output(k, &open_file.master_key, v) {
                        Ok(output::Output::OpenText(s)) => Some(format!("{}: {}", k, s)),
                        Ok(output::Output::PrivateText(s)) => Some(format!("{}: {}", k, String::from_utf8(Vec::from(s.unsecure())).expect("Couldn't decode UTF-8"))),
                        _ => None,
                    }
                }).collect::<Vec<_>>().join("\n");
                writer.write_record(&[file_path.to_string(), "".to_string(), "login".to_string(), name.to_string(), "".to_string(), extras, format!("https://{}", name), username, password, "".to_string()]).unwrap();
            }
            writer.flush().unwrap();
        },

        ("interact", _) | _ => interact::interact_entries(&mut open_file, debug),

    }
}

fn opt_or_env(matches: &clap::ArgMatches, opt_name: &str, env_name: &str) -> Option<String> {
    matches
        .value_of(opt_name)
        .map(|x| x.to_owned())
        .or(env::var_os(env_name).and_then(|s| s.into_string().ok()))
}

fn unwrap_for_opt(opt: Option<String>, name: &str) -> String {
    match opt {
        Some(s) => s,
        None => panic!("Option {} not found", name),
    }
}


text:
pluginManagement {
	repositories {
		gradlePluginPortal()
		google()
	}
	resolutionStrategy {
		eachPlugin {
			if (requested.id.id == "com.android.application") {
				useModule("com.android.tools.build:gradle:3.1.2")
			}
			if (requested.id.id.startsWith("org.jetbrains.kotlin")) {
				useVersion("1.2.41")
			}
		}
	}
}

rootProject.buildFileName = "build.gradle.kts"
include("app")


text:
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx10248m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true

org.gradle.script.lang.kotlin.accessors.auto=true

# Other performance stuff
org.gradle.configureondemand=false
android.enableBuildCache=false
kotlin.incremental=true


text:
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


text:
import java.util.Properties

allprojects {
	// Allow override for e.g. storing the project on a network drive, but building to an SSD
	val properties = Properties()
	properties.load(project.rootProject.file("local.properties").inputStream())
	buildDir = File(properties.getProperty("build-dir", "build"))
}


text:
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


text:
cmake_minimum_required(VERSION 3.6.0)

set(ANDROID_BUILD_MODE debug)

if(ANDROID_ABI STREQUAL x86)
    set(SODIUM_ABI i686)
elseif(ANDROID_ABI STREQUAL armeabi)
    set(SODIUM_ABI armv7)
elseif(ANDROID_ABI STREQUAL armeabi-v7a)
    set(SODIUM_ABI armv7-a)
elseif(ANDROID_ABI STREQUAL arm64-v8a)
    set(SODIUM_ABI armv8-a)
endif()

message("LIB DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")

add_library(libsodium SHARED IMPORTED)
set_target_properties(libsodium PROPERTIES
                      IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../../libsodium/libsodium-android-${SODIUM_ABI}/lib/libsodium.so
                      IMPORTED_NO_SONAME true)
include_directories( ${CMAKE_SOURCE_DIR}/../../libsodium/libsodium-android-${SODIUM_ABI}/include )
file(COPY ${CMAKE_SOURCE_DIR}/../../libsodium/libsodium-android-${SODIUM_ABI}/lib/libsodium.so
     DESTINATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/)

add_library(freepass-capi SHARED IMPORTED)
set_target_properties(freepass-capi PROPERTIES
                      IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../../capi/target/android-all/${ANDROID_BUILD_MODE}/${ANDROID_ABI}/libfreepass_capi.so
                      IMPORTED_NO_SONAME true)
include_directories( ${CMAKE_SOURCE_DIR}/../../capi )
file(COPY ${CMAKE_SOURCE_DIR}/../../capi/target/android-all/${ANDROID_BUILD_MODE}/${ANDROID_ABI}/libfreepass_capi.so
     DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})

find_library(log-lib log)

add_library(jniVault SHARED src/main/jni/jniVault.cpp)

target_link_libraries(jniVault freepass-capi libsodium ${log-lib})


text:
import com.android.build.api.sourcesets.AndroidSourceSet
import java.io.File

repositories {
	jcenter()
	google()
	mavenCentral()
	maven("https://jitpack.io")
}

plugins {
	id("com.android.application")
	kotlin("android")
	kotlin("android.extensions")
	kotlin("kapt")
}

android {
	buildToolsVersion("28.0.0 rc2")
	compileSdkVersion(27)

	defaultConfig {
		minSdkVersion(23)
		targetSdkVersion(27)
		applicationId = "technology.unrelenting.freepass"
		versionCode = 1
		versionName = "1.0"
		multiDexEnabled = true
		ndk {
			//abiFilters("x86")//, "arm64-v8a", "armeabi-v7a"//, "armeabi"
		}
		externalNativeBuild {
			cmake {
				arguments.addAll(arrayOf("-DANDROID_PLATFORM=android-24", "-DANDROID_STL=stlport_shared", "-DANDROID_CPP_FEATURES=exceptions"))
			}
        }
	}

	externalNativeBuild {
		cmake {
			setPath("CMakeLists.txt")
		}
	}

	dexOptions {
		preDexLibraries = true
		dexInProcess = true
	}

	dataBinding {
		isEnabled = true
	}

	buildTypes {
		getByName("release") {
            isMinifyEnabled = false
			proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
		}
		getByName("debug") {
            isJniDebuggable = true
		}
	}

	sourceSets {
        getByName("main") {
			//jni.srcDirs.clear()
			withGroovyBuilder {
				"jniLibs" {
					"srcDir"("../../capi/target/android-all/debug/")
				}
			}
        }
	}

	splits {
		abi {
			isEnable = true
			reset()
			include("x86", "arm64-v8a", "armeabi-v7a")//, "armeabi"
			isUniversalApk = true
		}
	}

	packagingOptions {
		exclude("META-INF/LICENSE")
    }

	applicationVariants.forEach { variant ->
		// Don"t move the task to the top level, it needs the `variant`!
		val javacppTask = task("build${variant.name.capitalize()}Javacpp") {
			inputs.file("src/main/kotlin/technology/unrelenting/freepass/Vault.kt")
			outputs.file("src/main/jni/jniVault.cpp")
			doLast {
				val path = configurations.getByName("compile")
						.find { it.name.startsWith("javacpp") }
						?.absolutePath
				javaexec {
					main = "org.bytedeco.javacpp.tools.Builder"
					classpath(path)
					val jc = variant.javaCompiler as JavaCompile
					args("-cp", jc.destinationDir,
							"-cp", jc.destinationDir.toString()
							.replace("intermediates/classes", "tmp/kotlin-classes")
							.replace("intermediates\\classes", "tmp\\kotlin-classes"),
							"-d", "src/main/jni", "-nocompile", "technology.unrelenting.freepass.Vault")
				}
			}
		}
		javacppTask.dependsOn(variant.javaCompiler)
		variant.externalNativeBuildTasks.forEach { it.dependsOn(javacppTask) }
	}
}

dependencies {
	implementation(kotlin("stdlib", "1.2.41"))
	implementation("com.upokecenter:cbor:2.5")
	implementation("io.reactivex.rxjava2:rxjava:2.1.0")
	implementation("io.reactivex.rxjava2:rxkotlin:2.0.3")
	//implementation("io.reactivex.rxjava2:rxandroid:2.0.1")
	//implementation("com.jakewharton.rxbinding2:rxbinding-kotlin:2.0.0")
	implementation("com.github.k-kagurazaka.rx-property-android:rx-property:3.1.0")
	implementation("com.github.k-kagurazaka.rx-property-android:rx-property-kotlin:3.1.0")
	implementation("org.bytedeco:javacpp:1.1")
}


text:
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.4/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}


text:
package technology.unrelenting.freepass

import android.app.Application
import android.test.ApplicationTestCase

/**
 * [Testing Fundamentals](http://d.android.com/tools/testing/testing_android.html)
 */
class ApplicationTest : ApplicationTestCase<Application>(Application::class.java)


text:
package technology.unrelenting.freepass

import org.junit.Test

import org.junit.Assert.*

/**
 * To work on unit tests, switch the Test Artifact in the Build Variants view.
 */
class ExampleUnitTest {
    @Test
    @Throws(Exception::class)
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}


text:
<?xml version="1.0" encoding="utf-8"?>
<manifest package="technology.unrelenting.freepass"
		  xmlns:android="http://schemas.android.com/apk/res/android">

	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

	<application
		android:allowBackup="true"
		android:icon="@mipmap/ic_launcher"
		android:label="Freepass"
		android:supportsRtl="true"
		android:theme="@style/AppTheme">

		<activity android:name=".LoginActivity">
			<intent-filter>
				<action android:name="android.intent.action.MAIN"/>
				<category android:name="android.intent.category.LAUNCHER"/>
			</intent-filter>
		</activity>

		<activity android:name=".VaultActivity">
		</activity>

		<activity android:name=".EntryActivity"
			android:parentActivityName=".VaultActivity">
		</activity>

	</application>

</manifest>


text:
<resources>

	<style name="AppTheme" parent="BaseTheme"/>

	<style name="BaseTheme" parent="android:Theme.Material.Light">
		<item name="android:colorPrimary">@color/primary</item>

		<item name="android:colorPrimaryDark">@color/primary_dark</item>

		<item name="android:colorControlHighlight">@color/accent_translucent</item>

		<item name="android:colorAccent">@color/accent</item>
	</style>

	<color name="primary">#00bcd4</color>
	<color name="primary_dark">#0097A7</color>
	<color name="accent">#FFEB3B</color>
	<color name="accent_translucent">#80FFEB3B</color>
	<color name="accent_bright">#FFF493</color>

</resources>


text:
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>


text:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

	<item android:title="Add"
		android:showAsAction="ifRoom"
		android:id="@+id/add_field" />
</menu>


text:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

	<item android:title="Add"
		android:showAsAction="ifRoom"
		android:id="@+id/add_entry" />
</menu>


text:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<ListView
		android:id="@+id/fields_list"
		android:layout_width="match_parent"
		android:layout_height="match_parent" />
</LinearLayout>


text:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
	<data>
		<variable name="vm" type="technology.unrelenting.freepass.FieldViewModel" />
		<import type="technology.unrelenting.freepass.FieldViewModel.FieldType" />
		<import type="android.view.View" />
	</data>

	<LinearLayout
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:padding="2dp"
		android:orientation="vertical">

		<EditText
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:ems="10"
			android:hint="Name"
			android:inputType="text"
			android:text="@={vm.field_name.value}" />

		<RadioGroup
			android:id="@+id/fld_type_picker"
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			android:layout_weight="1"
			android:orientation="horizontal"
			android:checkedButton="@={vm.field_type_radio.value}">

			<RadioButton
				android:id="@+id/fld_type_derived"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:layout_weight="1"
				android:text="Derived" />

			<RadioButton
				android:id="@+id/fld_type_stored"
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:layout_weight="1"
				android:text="Stored" />
		</RadioGroup>

		<GridLayout
			android:id="@+id/fld_derived_settings"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:columnCount="2"
			android:padding="2dp"
			android:visibility="@{vm.field_type.value == FieldType.Derived ? View.VISIBLE : View.GONE}">

			<TextView
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:text="Counter" />

			<EditText
				android:layout_width="0dp"
				android:layout_height="wrap_content"
				android:layout_gravity="fill_horizontal"
				android:ems="10"
				android:inputType="number"
                android:text="@={`` + vm.derived_counter.value}" />

			<TextView
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:text="Site name" />

			<EditText
				android:layout_width="0dp"
				android:layout_height="wrap_content"
				android:layout_gravity="fill_horizontal"
				android:ems="10"
				android:hint="Leave blank to use field name"
				android:inputType="text"
                android:text="@={vm.derived_site_name.value}" />

			<TextView
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:text="Usage" />

			<Spinner
				android:layout_width="0dp"
				android:layout_height="wrap_content"
				android:layout_gravity="fill_horizontal"
				android:paddingBottom="8dp"
				android:paddingTop="8dp" />

		</GridLayout>

		<GridLayout
			android:id="@+id/fld_stored_settings"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:columnCount="2"
			android:padding="2dp"
			android:visibility="@{vm.field_type.value == FieldType.Stored ? View.VISIBLE : View.GONE}">

			<TextView
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:text="Value" />

			<EditText
				android:layout_width="0dp"
				android:layout_height="wrap_content"
				android:layout_gravity="fill_horizontal"
				android:ems="10"
				android:inputType="text"
                android:text="@={vm.stored_data_string.value}" />

			<TextView
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:text="Usage" />

			<Spinner
				android:layout_width="0dp"
				android:layout_height="wrap_content"
				android:layout_gravity="fill_horizontal"
				android:paddingBottom="8dp"
				android:paddingTop="8dp" />

		</GridLayout>

	</LinearLayout>
</layout>


text:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/entry_wrapper_l"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"></LinearLayout>


text:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<ListView
		android:id="@+id/entries_list"
		android:layout_width="match_parent"
		android:layout_height="match_parent" />
</LinearLayout>


text:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<fragment
		android:id="@+id/fragment"
		android:name="technology.unrelenting.freepass.VaultFragment"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:layout_weight="1" />
</LinearLayout>


text:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"
	android:padding="30dip">

	<EditText
		android:id="@+id/lgn_name"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:hint="Name"
		android:inputType="text|textNoSuggestions"
		android:singleLine="true"
		android:textSize="18sp" />

	<EditText
		android:id="@+id/lgn_password"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:hint="Password"
		android:inputType="textNoSuggestions|textWebPassword"
		android:singleLine="true"
		android:textSize="18sp" />

	<Button
		android:id="@+id/lgn_go"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:text="Log in" />

</LinearLayout>


text:
package technology.unrelenting.freepass

import android.app.Activity
import android.app.Fragment
import android.databinding.DataBindingUtil
import android.os.Bundle
import android.view.*
import android.widget.BaseAdapter
//import com.jakewharton.rxbinding2.widget.*
import kotlinx.android.synthetic.main.entry.*
import technology.unrelenting.freepass.databinding.FieldBinding

import java.util.*

class EntryFragment: Fragment() {

	companion object {
		const val ENTRY_NAME = "entry_name"
	}

	var entryName = "New entry"
	var fieldModels = ArrayList<FieldViewModel>()
	val fieldListAdapter = FieldListAdapter(this)

	override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
		super.onCreateView(inflater, container, savedInstanceState)
		entryName = arguments.getString(ENTRY_NAME)
		val entryCode = Vault.getEntry(entryName)
		if (entryCode != null) {
			val entry = Entry.fromCbor(entryCode!!)
			entry.fields.forEach {
				fieldModels.add(FieldViewModel(it.key, it.value))
			}
		}
		setHasOptionsMenu(true)
		return inflater?.inflate(R.layout.entry, container, false)
	}

	override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
		super.onViewCreated(view, savedInstanceState)
		fields_list.adapter = fieldListAdapter
	}

	override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
		inflater?.inflate(R.menu.entry, menu)
	}

	override fun onOptionsItemSelected(item: MenuItem?): Boolean {
		when (item?.itemId) {
			R.id.add_field -> {
				fieldModels.add(FieldViewModel("New field"))
				fieldListAdapter.notifyDataSetChanged()
			}
		}
		return false
	}

	class FieldListAdapter(val fragment: EntryFragment): BaseAdapter() {
		var list = fragment.fieldModels
		var inflater: LayoutInflater? = null

		override fun getView(i: Int, v: View?, parent: ViewGroup?): View? {
			if (parent == null) return null
            inflater = inflater ?: (parent.context as? Activity)?.layoutInflater
			val binding = DataBindingUtil.getBinding<FieldBinding>(v!!)
				?: DataBindingUtil.inflate<FieldBinding>(inflater!!, R.layout.field, parent, false)
			binding.vm = getItem(i)
			return binding.root
//					val typeRadio = radioGroup {
//						orientation = RadioGroup.HORIZONTAL
//						val der = radioButton {  text = "Derived" }
//						val stor = radioButton {  text = "Stored" }
//						Log.w("WTF", "WAT")
//						check(if (model.field_type.get() == FieldViewModel.FieldType.Derived) der.id else stor.id)
//						checkedChanges().subscribe() {
//							Log.w("Check", it.toString())
//							model.field_type.set(if (it == der.id) FieldViewModel.FieldType.Derived else FieldViewModel.FieldType.Stored)
//						}
//					}.lparams { width = matchParent; below(nameEdit) }
//					val counterLabel = textView {
//						text = "Counter"
//					}.lparams { below(typeRadio) }
//					val counterEdit = editText {
//						inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
//						filters = arrayOf(NumberLimitsInputFilter(0, 4294967295))
//					}.lparams { below(typeRadio); rightOf(counterLabel) }
		}

        override fun getItem(position: Int): FieldViewModel {
            return list[position]
        }

		override fun getCount(): Int {
			return list.size
		}

		override fun getItemId(position: Int): Long {
			return position.toLong()
		}
	}
}


text:
package technology.unrelenting.freepass

import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.AsyncTask
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.login.*

class LoginActivity : Activity() {

	inner class OpenTask(private val ctx: Context) : AsyncTask<String, Unit, Unit>() {
		override fun doInBackground(vararg args: String) {
			Vault.open(args[0], args[1], args[2])
			println("Entry names: ${Vault.entryNames()}")
		}

		override fun onPostExecute(result: Unit?) {
			ctx.startActivity(Intent(ctx, VaultActivity::class.java))
		}
	}

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.login)
		lgn_go.setOnClickListener {
			val permission = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
			if (permission == PackageManager.PERMISSION_GRANTED) {
				storageReadAllowed = true
			} else {
				requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
			}
			if (!storageReadAllowed) {
				Toast.makeText(this, "You must allow reading files!", Toast.LENGTH_SHORT).show()
			}
			println("Dir: ${getExternalFilesDir(null).path}")
			OpenTask(this).execute("${getExternalFilesDir(null).path}/testvault", lgn_name.text.toString(), lgn_password.text.toString())
		}
	}

	var storageReadAllowed = false

	override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults)
		for ((perm, res) in permissions.zip(grantResults.toList())) {
			if (perm == Manifest.permission.READ_EXTERNAL_STORAGE) {
				storageReadAllowed = res == PackageManager.PERMISSION_GRANTED
			}
		}
	}

}


text:
package technology.unrelenting.freepass

import org.bytedeco.javacpp.*
import org.bytedeco.javacpp.annotation.*
import java.io.File

class VaultException(reason: String) : Exception(reason) {}

@Platform(library = "jniVault",
          cinclude = arrayOf("freepass_capi.h"))
object Vault {
	var masterKey: Pointer? = null
	var vaultObj: Pointer? = null
	var entriesKey: Pointer? = null
	var filePath: String? = null
	var isOpen = false

	init {
		//Loader.load() // javacpp's loader tries to compute the full path inside the jar for some reason
		                // and ends up with /arm/ instead of /armeabi-v7a/ or /arm64-v7a/
		System.loadLibrary("jniVault")
		freepass_init()
	}

	fun open(path: String, username: String, password: String) {
		if (isOpen) {
			throw VaultException("Already open")
		}
		masterKey = rusterpassword_gen_master_key(password, username)
		if (masterKey == null || masterKey!!.isNull) {
			throw VaultException("Got null master key")
		}
		val outerKey = freepass_gen_outer_key(masterKey!!)
		val entriesKey = freepass_gen_entries_key(masterKey!!)

		val file = File(path)
		vaultObj = if (file.exists()) {
			if (!file.canRead()) {
				throw VaultException("Not allowed to read file ${file.absolutePath}")
			}
			freepass_open_vault(file.absolutePath, entriesKey, outerKey)
		} else {
			freepass_new_vault(entriesKey, outerKey)
		}
		freepass_free_outer_key(outerKey)

		if (vaultObj == null || vaultObj!!.isNull) {
			rusterpassword_free_master_key(masterKey!!)
			freepass_free_entries_key(entriesKey)
			throw VaultException("Got null vault")
		}

		filePath = file.absolutePath
		isOpen = true
	}

	fun entryNames(): List<String> {
		if (vaultObj == null) {
			return listOf()
		}
		val iter = freepass_vault_get_entry_names_iterator(vaultObj!!)
		var curr: BytePointer? = freepass_entry_names_iterator_next(iter)
		val result = mutableListOf<String>()
		while (curr != null && !curr.isNull) {
			result.add(curr.string)
			freepass_free_entry_name(curr)
			curr = freepass_entry_names_iterator_next(iter)
		}
		freepass_free_entry_names_iterator(iter)
		return result.toList()
	}

	fun close() {
		if (masterKey != null) {
			rusterpassword_free_master_key(masterKey!!)
		}
		masterKey = null
		if (entriesKey != null) {
			freepass_free_entries_key(entriesKey!!)
		}
		entriesKey = null
		if (vaultObj != null) {
			freepass_close_vault(vaultObj!!)
		}
		vaultObj = null
		isOpen = false
	}

	@JvmStatic external fun rusterpassword_gen_master_key(username: String, password: String): Pointer
	@JvmStatic external fun rusterpassword_free_master_key(@Cast("secstr_t*") master_key: Pointer): Unit

	@JvmStatic external fun freepass_init(): Unit

	@JvmStatic external fun freepass_gen_outer_key(@Cast("secstr_t*") master_key: Pointer): Pointer
	@JvmStatic external fun freepass_gen_entries_key(@Cast("secstr_t*") master_key: Pointer): Pointer
	@JvmStatic external fun freepass_free_outer_key(@Cast("secstr_t*") outer_key: Pointer): Unit
	@JvmStatic external fun freepass_free_entries_key(@Cast("secstr_t*") entries_key: Pointer): Unit

	@JvmStatic external fun freepass_open_vault(path: String, @Cast("secstr_t*") entries_key: Pointer, @Cast("secstr_t*") outer_key: Pointer): Pointer
	@JvmStatic external fun freepass_new_vault(@Cast("secstr_t*") entries_key: Pointer, @Cast("secstr_t*") outer_key: Pointer): Pointer
	@JvmStatic external fun freepass_close_vault(@Cast("vault_t*") vault: Pointer): Unit

	@JvmStatic external fun freepass_vault_get_entry_names_iterator(@Cast("vault_t*") vault: Pointer): Pointer
	@JvmStatic external @Cast("signed char*") fun freepass_entry_names_iterator_next(@Cast("string_iter_t*") iter: Pointer): BytePointer
	@JvmStatic external fun freepass_free_entry_name(@Cast("char*") name: BytePointer): Unit
	@JvmStatic external fun freepass_free_entry_names_iterator(@Cast("string_iter_t*") iter: Pointer): Unit

	@JvmStatic @ByVal external fun freepass_vault_get_entry_cbor(@Cast("vault_t*") vault: Pointer, name: String): vector_t
	@JvmStatic external fun freepass_free_entry_cbor(@ByVal cbor: vector_t)
	@JvmStatic external fun freepass_vault_put_entry_cbor(@Cast("vault_t*") vault: Pointer, name: String, @Cast("uint8_t*") data: BytePointer, @Cast("size_t") len: Int)

	class vector_t(p: Pointer) : Pointer(p) {
		init { Loader.load() }
		external fun allocate()
		@Cast("uint8_t*") @MemberGetter external fun data(): BytePointer
		@MemberGetter external fun len(): Int
		@MemberGetter external fun cap(): Int
	}
}


text:
package technology.unrelenting.freepass

import android.app.Activity
import android.os.Bundle

class EntryActivity: Activity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)

		if (!Vault.isOpen) {
			finish()
		}

		setContentView(R.layout.entry_wrapper)
		if (savedInstanceState == null) {
			fragmentManager.beginTransaction()
					.add(R.id.entry_wrapper_l, EntryFragment().let { it.arguments = intent.extras; it })
					.commit()
		}
	}

}


text:
package technology.unrelenting.freepass

import jp.keita.kagurazaka.rxproperty.RxProperty
import java.nio.charset.Charset

class FieldViewModel(name: String, field: Field?) {
	companion object {
		val UTF8 = Charset.forName("UTF-8")
	}

	enum class FieldType {
		Stored, Derived
	}

	val field_name = RxProperty(name)
	val field_type = RxProperty<FieldType?>()
	val field_type_radio = RxProperty(field_type.map {
		when (it) {
			FieldType.Stored -> R.id.fld_type_stored
			else -> R.id.fld_type_derived
		}
	}).let {
		it.subscribe {
			when (it) {
				R.id.fld_type_derived -> field_type.set(FieldType.Derived)
				R.id.fld_type_stored -> field_type.set(FieldType.Stored)
			}
		}
		it
	}
	val derived_counter = RxProperty<Int>(0)
	val derived_site_name = RxProperty<String>()
	val derived_usage = RxProperty<DerivedUsage?>()
	val stored_data = RxProperty<ByteArray?>()
	val stored_data_string = RxProperty<String>(stored_data.map {
		if (it == null) ""
		else try {
			String(it, UTF8)
		} catch (e: Exception) {
			""
		}
	}).let {
		it.subscribe {
			if (it != null && it.isNotEmpty())
				stored_data.set((it as java.lang.String).getBytes("UTF-8"))
		}
		it
	}
	val stored_usage = RxProperty<StoredUsage?>()

	init {
		if (field is Field.Derived) {
			field_type.set(FieldType.Derived)
			derived_counter.set(field.counter)
			derived_site_name.set(field.site_name.orEmpty())
			derived_usage.set(field.usage)
		} else if (field is Field.Stored) {
			field_type.set(FieldType.Stored)
			stored_data.set(field.data)
			stored_usage.set(field.usage)
		}
	}

	constructor(name: String) : this(name, null)
}


text:
package technology.unrelenting.freepass

import com.upokecenter.cbor.CBORObject

data class Entry(var fields: Map<String, Field> = emptyMap()) {
	companion object {
        fun fromCbor(cborObject: CBORObject): Entry {
			val result = mutableMapOf<String, Field>()
			val fields = cborObject["fields"]
			fields.keys.forEach {
				result.put(it.AsString(), Field.fromCbor(fields[it]))
			}
			return Entry(result)
        }
	}
}


text:
package technology.unrelenting.freepass

import android.app.Activity
import android.os.Bundle

class VaultActivity: Activity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)

		// XXX TESTING
        //Vault.open("${getExternalFilesDir(null).path}/testvault", "Test", "test")

		if (!Vault.isOpen) {
			finish()
		}

		setContentView(R.layout.vault_wrapper)
	}

}


text:
package technology.unrelenting.freepass

import com.upokecenter.cbor.CBORObject
import com.upokecenter.cbor.CBORType

sealed class DerivedUsage {
	data class Password(val template: String) : DerivedUsage()
	data class Ed25519Key(val usage: String) : DerivedUsage()
	object RawKey : DerivedUsage()
	object Unreadable : DerivedUsage()

	companion object {
		fun fromCbor(cborObject: CBORObject): DerivedUsage {
			return when (cborObject.type) {
				CBORType.TextString -> when (cborObject.AsString()) {
					"RawKey" -> RawKey
					else -> null
				}
				CBORType.Array -> {
					val (variant, argsObj) = cborObject.values.toList()
					when (variant.AsString()) {
						"Password" -> Password(argsObj.AsNullableString() ?: "Maximum")
						"Ed25519Key" -> Ed25519Key(argsObj.AsNullableString() ?: "SSH")
						else -> null
					}
				}
				else -> null
			} ?: Unreadable
		}
	}
}

enum class StoredUsage {
	Unreadable, Text, Password
}

sealed class Field {
	data class Derived(val counter: Int, val site_name: String?, val usage: DerivedUsage) : Field()
	data class Stored(val data: ByteArray, val usage: StoredUsage) : Field()
	object Unreadable : Field()

	companion object {
        fun fromCbor(cborObject: CBORObject): Field {
			val (variant, argsObj) = cborObject.values.toList()
			return when (variant.AsString()) {
				"Derived" -> {
					val counter = argsObj.get("counter").AsInt32()
					val site_name = argsObj.get("site_name").AsNullableString()
					val usage = DerivedUsage.fromCbor(argsObj.get("usage"))
					Derived(counter, site_name, usage)
				}
				"Stored" -> {
					val data = argsObj.get("data").GetByteString()
					val usage = StoredUsage.valueOf(argsObj.get("usage").AsString())
					Stored(data, usage)
				}
				else -> null
			} ?: Unreadable
		}
	}
}


text:
package technology.unrelenting.freepass

import android.text.InputFilter
import android.text.Spanned

class NumberLimitsInputFilter(val min: Long, val max: Long) : InputFilter {
	override fun filter(src: CharSequence?, start: Int, end: Int, dst: Spanned?, dstart: Int, dend: Int): CharSequence? {
		try {
			val num = java.lang.Long.parseLong(dst.toString() + src.toString())
			if (num >= min && num <= max) return null
		} catch (e: NumberFormatException) {}
		return ""
	}
}


text:
package technology.unrelenting.freepass

import android.app.Fragment
import android.content.Intent
import android.os.Bundle
import android.view.*
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.vault.*

class VaultFragment: Fragment() {

	override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
		super.onCreateView(inflater, container, savedInstanceState)
		setHasOptionsMenu(true)
		return inflater?.inflate(R.layout.vault, container, false)
	}

	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
		super.onViewCreated(view, savedInstanceState)
		return
		val entryNames = Vault.entryNames().toTypedArray()
		entries_list.adapter = ArrayAdapter<String>(this.context,
				android.R.layout.simple_list_item_1,
				entryNames)
		entries_list.setOnItemClickListener { adapterView, view, i, l ->
			openEntry(entryNames[i])
		}
	}

	override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
		inflater?.inflate(R.menu.vault, menu)
	}

	override fun onOptionsItemSelected(item: MenuItem?): Boolean {
		when (item?.itemId) {
			R.id.add_entry -> {
				openEntry("New entry")
			}
		}
		return false
	}

	fun openEntry(name: String) {
		println(name)
		startActivity(Intent(this.context, EntryActivity::class.java)
				.let { it.putExtra(EntryFragment.ENTRY_NAME, name); it })
	}

}


text:
package technology.unrelenting.freepass

// Separate file to prevent JavaCPP from loading CBOR

import com.upokecenter.cbor.CBORObject

fun Vault.getEntry(name: String): CBORObject? {
	if (vaultObj == null) {
		return null
	}
	return freepass_vault_get_entry_cbor(vaultObj!!, name).let {
		if (it.len() < 1) return null
		val arr = ByteArray(it.len())
		it.data().get(arr, 0, it.len())
		val objs = CBORObject.DecodeFromBytes(arr).values.take(2)
		val dataval = objs[0]
		//val metaval = objs[1]
		freepass_free_entry_cbor(it)
		dataval
	}
}

fun CBORObject.AsNullableString(): String? {
	return if (isNull) null else AsString()
}


text:
#Tue Sep 05 21:57:49 MSK 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip
# https://github.com/gradle/kotlin-dsl/releases


text:
ZF-Boilerplate
==============

Zend Framework (ZF) Boilerplate is a pre-packaged, pre-configured Zend Framework - based blueprint for your
enterprise grade PHP application. It combines state-of-the-art tools, frameworks and ideas to
bootstrap your projects with nearly zero work.

ZF-Boilerplate isn't a framework, nor does it claim to be complete. It's the sum of the latest and greatest libraries,
tools and scripts to get your ZF based project off the ground quickly and to speed up your development process.

Features
--------

* Zend Framework on Steriods: ZF-Boilerplate is based on a current version of Zend Framework, the enterprise PHP framework by Zend, the company behind PHP. For ZF-Boilerplate, Zend Framework has been extended, e.g. by a Service Locator and annotation based Dependency Injection.
* Doctrine 2 tightly integrated: Doctrine 2 is the de-facto ORM system for PHP, mapping objects to relational databases. Doctrine 2 takes away all the pain working in both worlds. With ZF-Boilerplate, Doctrine 2 works out-of-the-box. But there is even more: It brings the tools for managing the database and dealing with data fixtures!
* Virtual Machine based Development: With ZF Boilerplate, there is no need to set up a production-like runtime environment on your local box. ZF Boilerplate ships with a Virtual Machine (VM) that brings all applications and tools required to run and develop a Zend Framework application. The VM is based on Virtual Box, is maintained by Vagrant and provisioned by Chef.
* Enterprise Search: Nearly all applications need search functionality. For many, search even is the most important part. Consider Online-Shops, Marketplaces or News Sites. They all heavily rely on search. ZF-Boilerplate ships with ElasticSearch, an enterprise, cloud-focused search technology pre-installed, pre-configured and ready to be used.
* Test Driven Development: With Unit Testing available out-of-the-box, ZF-Boilerplate perfectly supports TDD and doesn't allow for any reason not to do "test-first" with your next ZF-Project. Includes Testdox creation and Code Coverage reports.
* Domain Driven Design: ZF-Boilerplate greatly supports Domain Driven Design and lets you easily model your Services, Entities, Repositories and Value Objects in plain PHP minimizing dependencies on other application components.
* SOAP Webservice: A serious webapp without a proper API? Naaa. ZF-Boilerplate gives you a SOAP based a API that is combat-ready right from the beginning. No setup, no config, no hassle. Just hook in the functions you want to expose via the API. Client application authentification via API token is certainly right built-in.
* Software Minining: ZF-Boilerplate comes with a bunch of the latest and greatest Software Mining tools available such as PHP_Depend and PHP_MD. Check the coding style of all your code with just a single command.
* Application Documentation: Documentation usually sucks because its too hard to get it in shape. ZF-Boilerplate brings Docblox for automatically generating your application's api documentation and DocBook support for authoring your users manual with ease. All installed, configured and ready to be used.

Installation
------------

Please refer to: http://zf-boilerplate.com/getting-started/

Roadmap
-------

Please refer to: http://zf-boilerplate.com/roadmap/

License
-------
* Zend Framework: New BSD License (http://framework.zend.com/license)
* Doctrine 2: GNU Lesser General Public License (http://www.opensource.org/licenses/lgpl-license.php)
* Symfony Dependency Injection: MIT License MIT License (http://www.opensource.org/licenses/mit-license.php)
* Chef Cookbooks: Apache License Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
* HTML 5 Boilerplate: The Unlicense (http://unlicense.org)
* Vagrant: MIT License (http://www.opensource.org/licenses/mit-license.php)
* Jenkins Template: Creative Commons Attribution-ShareAlike 3.0 Unported License (http://creativecommons.org/licenses/by-sa/3.0/)
* Bisna Library: GNU Lesser General Public License (http://www.opensource.org/licenses/lgpl-license.php)
* Elastica Library: Apache License Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
* EasyBib Library: MIT License (http://www.opensource.org/licenses/mit-license.php)
* Twitter Bootstrap: Apache License Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
* PHPRack: GNU General Public License (http://www.ohloh.net/licenses/GPLv3)
* Everything else: The Unlicense (http://unlicense.org)

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>

repo_name: michael-romer/zf-boilerplate

repo_language: PHP

mime_type: text/html


text:
Vagrant::Config.run do |config|
  config.vm.customize [
    "modifyvm", :id,
    "--memory", "1024",
    "--cpus", "2"
  ]
  config.vm.box = "precise64"
  config.vm.forward_port 80, 8080
  config.vm.forward_port 3306, 3306
  config.ssh.max_tries = 50
  config.ssh.timeout   = 300
  config.vm.provision :chef_solo do |chef|
     chef.cookbooks_path = "data/cookbooks"
     chef.add_recipe("vagrant_main")
     chef.log_level = :debug
     # Configure http(s) proxy if needed (requires Vagrant >= 0.7.4)
     # chef.http_proxy = "http://proxy.vmware.com:3128"
     # chef.https_proxy = "http://proxy.vmware.com:3128"
     # chef.http_proxy_user = "my_username"
     # chef.http_proxy_pass = "foo"
     chef.json.merge!({ :mysql => { :server_root_password => "" } })
  end
end


text:
README
======

This directory should be used to place project specfic documentation including
but not limited to project notes, generated API/phpdoc documentation, or
manual files generated or hand written.  Ideally, this directory would remain
in your development environment only and should not be deployed with your
application to it's final production location.

Setting Up Your VHOST
=====================

The following is a sample VHOST you might want to consider for your project.

<VirtualHost *:80>
   DocumentRoot "C:/xampp/htdocs/doctrine2/public"
   ServerName .local

   # This should be omitted in the production environment
   SetEnv APPLICATION_ENV development

   <Directory "C:/xampp/htdocs/doctrine2/public">
       Options Indexes MultiViews FollowSymLinks
       AllowOverride All
       Order allow,deny
       Allow from all
   </Directory>

</VirtualHost>


text:
<?php
echo shell_exec("docblox run -d ../application,../library/App -t api");


text:
<?php
echo shell_exec("phd --docbook manual/source.xml -o manual/html/");


text:
<?xml version="1.0" encoding="UTF-8"?>
 <book xml:id="myapp_users_manual" xmlns="http://docbook.org/ns/docbook" version="5.0">
   <title>Introduction</title>
   <chapter xml:id="chapter_1">
     <title>Welcome to MyApp</title>
     <para>A very warm welcome to MyApp!</para>
     <para>In the following, you will learn all the details about MyApp ...</para>
   </chapter>
   <chapter xml:id="chapter_2">
     <title>Main Concepts</title>
     <para>To understand how MyApp works, it important to go over some fundamental concepts first ...</para>
   </chapter>
 </book>