Repository: kpcyrd/sn0int (language: rust)

Issue:
Delete command
Right now we can't delete anything (we can only `noscope` it). This should be fairly simple to add.


Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 060d42dc..9c3d4788 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,7 @@
 language: rust
 
+cache: cargo
+
 matrix:
   include:
   - os: linux
diff --git a/migrations/2018-10-26-001932_cascade-delete/down.sql b/migrations/2018-10-26-001932_cascade-delete/down.sql
new file mode 100644
index 00000000..86d9167c
--- /dev/null
+++ b/migrations/2018-10-26-001932_cascade-delete/down.sql
@@ -0,0 +1,126 @@
+PRAGMA foreign_keys=off;
+
+-- domains
+
+ALTER TABLE domains RENAME TO _domains_old;
+
+CREATE TABLE domains (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    CONSTRAINT domain_unique UNIQUE (value)
+);
+
+INSERT INTO domains (id, value, unscoped)
+    SELECT id, value, unscoped
+    FROM _domains_old;
+
+DROP TABLE _domains_old;
+
+-- subdomains
+
+ALTER TABLE subdomains RENAME TO _subdomains_old;
+
+CREATE TABLE subdomains (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    domain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    resolvable BOOLEAN,
+    FOREIGN KEY(domain_id) REFERENCES domains(id),
+    CONSTRAINT subdomain_unique UNIQUE (value)
+);
+
+INSERT INTO subdomains (id, domain_id, value, unscoped, resolvable)
+    SELECT id, domain_id, value, unscoped, resolvable
+    FROM _subdomains_old;
+
+DROP TABLE _subdomains_old;
+
+-- subdomain_ipaddrs
+
+ALTER TABLE subdomain_ipaddrs RENAME TO _subdomain_ipaddrs_old;
+
+CREATE TABLE subdomain_ipaddrs (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    ip_addr_id INTEGER NOT NULL,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id),
+    FOREIGN KEY(ip_addr_id) REFERENCES ipaddrs(id),
+    CONSTRAINT subdomain_ipaddr_unique UNIQUE (subdomain_id, ip_addr_id)
+);
+
+INSERT INTO subdomain_ipaddrs (id, subdomain_id, ip_addr_id)
+    SELECT id, subdomain_id, ip_addr_id
+    FROM _subdomain_ipaddrs_old;
+
+DROP TABLE _subdomain_ipaddrs_old;
+
+-- urls
+
+ALTER TABLE urls RENAME TO _urls_old;
+
+CREATE TABLE urls (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    status INTEGER,
+    body BLOB,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    online BOOLEAN,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id),
+    CONSTRAINT url_unique UNIQUE (value)
+);
+
+INSERT INTO urls (id, subdomain_id, value, status, body, unscoped, online)
+    SELECT id, subdomain_id, value, status, body, unscoped, online
+    FROM _urls_old;
+
+DROP TABLE _urls_old;
+
+-- emails
+
+ALTER TABLE emails RENAME TO _emails_old;
+
+CREATE TABLE emails (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    valid BOOLEAN,
+    CONSTRAINT email_unique UNIQUE (value)
+);
+
+INSERT INTO emails (id, value, unscoped, valid)
+    SELECT id, value, unscoped, valid
+    FROM _emails_old;
+
+DROP TABLE _emails_old;
+
+-- ipaddrs
+
+ALTER TABLE ipaddrs RENAME TO _ipaddrs_old;
+
+CREATE TABLE ipaddrs (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    family VARCHAR NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    continent VARCHAR,
+    continent_code VARCHAR,
+    country VARCHAR,
+    country_code VARCHAR,
+    city VARCHAR,
+    latitude FLOAT,
+    longitude FLOAT,
+    asn INTEGER,
+    as_org VARCHAR,
+    CONSTRAINT ipaddr_unique UNIQUE (value)
+);
+
+INSERT INTO ipaddrs (id, family, value, unscoped, continent, continent_code, country, country_code, city, latitude, longitude, asn, as_org)
+  SELECT id, family, value, unscoped, continent, continent_code, country, country_code, city, latitude, longitude, asn, as_org
+  FROM _ipaddrs_old;
+
+DROP TABLE _ipaddrs_old;
+
+PRAGMA foreign_keys=on;
diff --git a/migrations/2018-10-26-001932_cascade-delete/up.sql b/migrations/2018-10-26-001932_cascade-delete/up.sql
new file mode 100644
index 00000000..8373384e
--- /dev/null
+++ b/migrations/2018-10-26-001932_cascade-delete/up.sql
@@ -0,0 +1,126 @@
+PRAGMA foreign_keys=off;
+
+-- domains
+
+ALTER TABLE domains RENAME TO _domains_old;
+
+CREATE TABLE domains (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    CONSTRAINT domain_unique UNIQUE (value)
+);
+
+INSERT INTO domains (id, value, unscoped)
+    SELECT id, value, unscoped
+    FROM _domains_old;
+
+DROP TABLE _domains_old;
+
+-- subdomains
+
+ALTER TABLE subdomains RENAME TO _subdomains_old;
+
+CREATE TABLE subdomains (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    domain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    resolvable BOOLEAN,
+    FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE,
+    CONSTRAINT subdomain_unique UNIQUE (value)
+);
+
+INSERT INTO subdomains (id, domain_id, value, unscoped, resolvable)
+    SELECT id, domain_id, value, unscoped, resolvable
+    FROM _subdomains_old;
+
+DROP TABLE _subdomains_old;
+
+-- subdomain_ipaddrs
+
+ALTER TABLE subdomain_ipaddrs RENAME TO _subdomain_ipaddrs_old;
+
+CREATE TABLE subdomain_ipaddrs (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    ip_addr_id INTEGER NOT NULL,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id) ON DELETE CASCADE,
+    FOREIGN KEY(ip_addr_id) REFERENCES ipaddrs(id) ON DELETE CASCADE,
+    CONSTRAINT subdomain_ipaddr_unique UNIQUE (subdomain_id, ip_addr_id)
+);
+
+INSERT INTO subdomain_ipaddrs (id, subdomain_id, ip_addr_id)
+    SELECT id, subdomain_id, ip_addr_id
+    FROM _subdomain_ipaddrs_old;
+
+DROP TABLE _subdomain_ipaddrs_old;
+
+-- urls
+
+ALTER TABLE urls RENAME TO _urls_old;
+
+CREATE TABLE urls (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    subdomain_id INTEGER NOT NULL,
+    value VARCHAR NOT NULL,
+    status INTEGER,
+    body BLOB,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    online BOOLEAN,
+    FOREIGN KEY(subdomain_id) REFERENCES subdomains(id) ON DELETE CASCADE,
+    CONSTRAINT url_unique UNIQUE (value)
+);
+
+INSERT INTO urls (id, subdomain_id, value, status, body, unscoped, online)
+    SELECT id, subdomain_id, value, status, body, unscoped, online
+    FROM _urls_old;
+
+DROP TABLE _urls_old;
+
+-- emails
+
+ALTER TABLE emails RENAME TO _emails_old;
+
+CREATE TABLE emails (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    valid BOOLEAN,
+    CONSTRAINT email_unique UNIQUE (value)
+);
+
+INSERT INTO emails (id, value, unscoped, valid)
+    SELECT id, value, unscoped, valid
+    FROM _emails_old;
+
+DROP TABLE _emails_old;
+
+-- ipaddrs
+
+ALTER TABLE ipaddrs RENAME TO _ipaddrs_old;
+
+CREATE TABLE ipaddrs (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    family VARCHAR NOT NULL,
+    value VARCHAR NOT NULL,
+    unscoped BOOLEAN DEFAULT 0 NOT NULL,
+    continent VARCHAR,
+    continent_code VARCHAR,
+    country VARCHAR,
+    country_code VARCHAR,
+    city VARCHAR,
+    latitude FLOAT,
+    longitude FLOAT,
+    asn INTEGER,
+    as_org VARCHAR,
+    CONSTRAINT ipaddr_unique UNIQUE (value)
+);
+
+INSERT INTO ipaddrs (id, family, value, unscoped, continent, continent_code, country, country_code, city, latitude, longitude, asn, as_org)
+  SELECT id, family, value, unscoped, continent, continent_code, country, country_code, city, latitude, longitude, asn, as_org
+  FROM _ipaddrs_old;
+
+DROP TABLE _ipaddrs_old;
+
+PRAGMA foreign_keys=on;
diff --git a/src/cmd/delete_cmd.rs b/src/cmd/delete_cmd.rs
new file mode 100644
index 00000000..0f50d717
--- /dev/null
+++ b/src/cmd/delete_cmd.rs
@@ -0,0 +1,50 @@
+use errors::*;
+
+use db;
+use shell::Readline;
+use structopt::StructOpt;
+use models::*;
+use term;
+
+
+#[derive(Debug, StructOpt)]
+pub enum Args {
+    #[structopt(name="domains")]
+    Domains(Filter),
+    #[structopt(name="subdomains")]
+    Subdomains(Filter),
+    #[structopt(name="ipaddrs")]
+    IpAddrs(Filter),
+    #[structopt(name="urls")]
+    Urls(Filter),
+    #[structopt(name="emails")]
+    Emails(Filter),
+}
+
+#[derive(Debug, StructOpt)]
+pub struct Filter {
+    args: Vec<String>,
+}
+
+impl Filter {
+    pub fn parse(&self) -> Result<db::Filter> {
+        db::Filter::parse(&self.args)
+    }
+}
+
+pub fn run(rl: &mut Readline, args: &[String]) -> Result<()> {
+    let args = Args::from_iter_safe(args)?;
+    let rows = match args {
+        Args::Domains(filter) => delete::<Domain>(rl, &filter),
+        Args::Subdomains(filter) => delete::<Subdomain>(rl, &filter),
+        Args::IpAddrs(filter) => delete::<IpAddr>(rl, &filter),
+        Args::Urls(filter) => delete::<Url>(rl, &filter),
+        Args::Emails(filter) => delete::<Email>(rl, &filter),
+    }?;
+    term::info(&format!("Deleted {} rows", rows));
+    Ok(())
+}
+
+fn delete<T: Model + Detailed>(rl: &mut Readline, filter: &Filter) -> Result<usize> {
+    rl.db().delete::<T>(&filter.parse()?)
+}
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index d6f383b6..8e87c4c8 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -1,4 +1,5 @@
 pub mod add_cmd;
+pub mod delete_cmd;
 pub mod switch_db_cmd;
 pub mod run_cmd;
 pub mod use_cmd;
diff --git a/src/complete.rs b/src/complete.rs
index e135790c..bb2d680e 100644
--- a/src/complete.rs
+++ b/src/complete.rs
@@ -94,6 +94,7 @@ impl Completer for CmdCompleter {
                         Ok((0, results))
                     }
                 },
+                Command::Delete => self.filter("delete", &cmd),
                 Command::Mod => {
                     // we can only complete the 2nd argument
                     if args != 2 {
diff --git a/src/db.rs b/src/db.rs
index b9945bd5..ace59a89 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -302,6 +302,10 @@ impl Database {
     pub fn noscope<T: Scopable>(&self, filter: &Filter) -> Result<usize> {
         T::noscope(self, filter)
     }
+
+    pub fn delete<T: Model>(&self, filter: &Filter) -> Result<usize> {
+        T::delete(self, filter)
+    }
 }
 
 #[derive(Debug, PartialEq)]
diff --git a/src/models/domain.rs b/src/models/domain.rs
index 0c61e84c..8abffa60 100644
--- a/src/models/domain.rs
+++ b/src/models/domain.rs
@@ -38,6 +38,14 @@ impl Model for Domain {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::domains::dsl::*;
+
+        diesel::delete(domains.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::domains::dsl::*;
 
diff --git a/src/models/email.rs b/src/models/email.rs
index abe8da49..4bb6f586 100644
--- a/src/models/email.rs
+++ b/src/models/email.rs
@@ -39,6 +39,14 @@ impl Model for Email {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::emails::dsl::*;
+
+        diesel::delete(emails.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::emails::dsl::*;
 
diff --git a/src/models/ipaddr.rs b/src/models/ipaddr.rs
index 8afb8d3b..9b52dae1 100644
--- a/src/models/ipaddr.rs
+++ b/src/models/ipaddr.rs
@@ -50,6 +50,14 @@ impl Model for IpAddr {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::ipaddrs::dsl::*;
+
+        diesel::delete(ipaddrs.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::ipaddrs::dsl::*;
 
diff --git a/src/models/mod.rs b/src/models/mod.rs
index 6e2de55f..e48db38d 100644
--- a/src/models/mod.rs
+++ b/src/models/mod.rs
@@ -53,6 +53,8 @@ pub trait Model: Sized {
 
     fn filter(db: &Database, filter: &Filter) -> Result<Vec<Self>>;
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize>;
+
     fn by_id(db: &Database, id: i32) -> Result<Self>;
 
     fn id(db: &Database, query: &Self::ID) -> Result<i32>;
diff --git a/src/models/subdomain.rs b/src/models/subdomain.rs
index 2f3be9db..234cf904 100644
--- a/src/models/subdomain.rs
+++ b/src/models/subdomain.rs
@@ -42,6 +42,14 @@ impl Model for Subdomain {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::subdomains::dsl::*;
+
+        diesel::delete(subdomains.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::subdomains::dsl::*;
 
diff --git a/src/models/subdomain_ipaddr.rs b/src/models/subdomain_ipaddr.rs
index 871c8109..0ce6d1ee 100644
--- a/src/models/subdomain_ipaddr.rs
+++ b/src/models/subdomain_ipaddr.rs
@@ -1,4 +1,5 @@
 use errors::*;
+use diesel;
 use diesel::prelude::*;
 use models::*;
 use std::net;
@@ -34,6 +35,14 @@ impl Model for SubdomainIpAddr {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::subdomain_ipaddrs::dsl::*;
+
+        diesel::delete(subdomain_ipaddrs.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::subdomain_ipaddrs::dsl::*;
 
diff --git a/src/models/url.rs b/src/models/url.rs
index 88196af0..0dbf2b28 100644
--- a/src/models/url.rs
+++ b/src/models/url.rs
@@ -44,6 +44,14 @@ impl Model for Url {
         Ok(results)
     }
 
+    fn delete(db: &Database, filter: &Filter) -> Result<usize> {
+        use schema::urls::dsl::*;
+
+        diesel::delete(urls.filter(filter.sql()))
+            .execute(db.db())
+            .map_err(Error::from)
+    }
+
     fn by_id(db: &Database, my_id: i32) -> Result<Self> {
         use schema::urls::dsl::*;
 
diff --git a/src/shell.rs b/src/shell.rs
index 6d109327..cf452d7c 100644
--- a/src/shell.rs
+++ b/src/shell.rs
@@ -26,6 +26,7 @@ use term::Prompt;
 pub enum Command {
     Add,
     Back,
+    Delete,
     Mod,
     Noscope,
     Run,
@@ -44,6 +45,7 @@ impl Command {
         match *self {
             Command::Add => "add",
             Command::Back => "back",
+            Command::Delete => "delete",
             Command::Mod => "mod",
             Command::Noscope => "noscope",
             Command::Run => "run",
@@ -62,6 +64,7 @@ impl Command {
             static ref COMMANDS: Vec<&'static str> = vec![
                 Command::Add.as_str(),
                 Command::Back.as_str(),
+                Command::Delete.as_str(),
                 Command::Mod.as_str(),
                 Command::Noscope.as_str(),
                 Command::Run.as_str(),
@@ -85,6 +88,7 @@ impl FromStr for Command {
         match s {
             "add" => Ok(Command::Add),
             "back" => Ok(Command::Back),
+            "delete" => Ok(Command::Delete),
             "mod" => Ok(Command::Mod),
             "noscope" => Ok(Command::Noscope),
             "run"  => Ok(Command::Run),
@@ -286,6 +290,7 @@ pub fn run_once(rl: &mut Readline) -> Result<bool> {
         Some((Command::Back, _)) => if rl.take_module().is_none() {
             return Ok(true);
         },
+        Some((Command::Delete, args)) => delete_cmd::run(rl, &args)?,
         Some((Command::Mod, args)) => mod_cmd::run(rl, &args)?,
         Some((Command::Noscope, args)) => noscope_cmd::run(rl, &args)?,
         Some((Command::Run, args)) => run_cmd::run(rl, &args)?,


Test patch: